Spring MVC @RequestBody map Optional <Enum>

I have a rest controller using this method:

@RequestMapping(value = "", method = { RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE })
    public ResponseEntity<?> add(@Valid @RequestBody MyModel myModel, Errors errors) {

        ...
        return new ResponseEntity<SomeObject>(someObject, HttpStatus.OK);
    }

      

In MyModel

there is a field isMeetingOrSale

that is an enumeration ( MeetingSaleFlag

):

public enum MeetingSaleFlag {
    MEETING("MEETING"),
    SALE("SALE");
    private final String name;       
    private MeetingSaleFlag(String s) { name = s; }
    public boolean equalsName(String otherName) {
       return (otherName == null) ? false : name.equals(otherName);
    }
    public String toString() { return this.name; }
}

      

and it can display json with field "isMeetingOrSale" : "MEETING"

but the value in the json may "isMeetingOrSale" : ""

or may not be entirely present, so in this case I want the field to be displayed to zero. If I change the feed toOptional<MeetingSaleFlag>

I got

Failed to read JSON: unable to create value of type [simple type, class java.util.Optional<MeetingSaleFlag>

] from String value ('MEETING'); no single String / factory method \\ n when [Source: java.io.PushbackInputStream@32b21158 ; line: 17, column: 18] (via the link chain: MyModel [\ isMeetingOrSale \ "]);

So the question is, how can I display an optional enum from json?

+3


source to share


2 answers


Thanks to Sotirios Delimanolis comment I was able to resolve the issue.

1) Add

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jdk8</artifactId>
        </dependency>

      

as an addiction.

2) Reconfigure the Jackson linker. Check in:

    @Bean
    @Primary
    public ObjectMapper jacksonObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new Jdk8Module());
        return mapper;
    }

      

OR do this to register the jdk8 module

/**
 * @return Jackson jdk8 module to be registered with every bean of type
 *         {@link ObjectMapper}
 */
@Bean
public Module jdk8JacksonModule() {
    return new Jdk8Module();
}

      



Another way to customize Jackson is to add beans like com.fasterxml.jackson.databind.Module to your context. They will be registered with each ObjectMapper bean, providing a global mechanism for injecting custom modules when new functionality is added to your application.

Doing so will only register the extra module and keep the inline Jackson config provided by Spring Boot.

3) result

Now that the property is not present in the posted json, it is mapped to null (This is not that great. I expected it to provide me with an option so I could use it .isPresent()

)
. When it's an empty string ( "isMeetingOrSale" : ""

), Jackson returns an error:

Failed to read JSON: Unable to instantiate MyModel from String value '': value not one of the declared names of Enum instances: [VAL1, VAL2]

which looks good to me.

Useful links: Jackson jdk8 module , Spring MVC configure Jackson

+4


source


This is an example of our codebase:

@NotNull // You probably don't want this
@JsonSerialize(using=CountrySerializer.class)
@JsonDeserialize(using=CountryDeserializer.class)
private CountryCode country;

      

where CountryCode is a complex enum (see nv-i18n ) and these are the classes to (de) serialize from / to JSON:

public class CountrySerializer extends JsonSerializer<CountryCode> {
    @Override
    public void serialize(CountryCode value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        jgen.writeString(value.getAlpha3()); // Takes the Alpha3 code
    }

    public Class<CountryCode> handledType() { return CountryCode.class; }
}

      



and

public class CountryDeserializer extends JsonDeserializer<CountryCode> {
    @Override
    public CountryCode deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
        // You can add here the check whether the field is empty/null
        return CountryCode.getByCode(jp.getText());
    }
}

      

You can easily reproduce the same scenario using MeetingSaleFlag instead of CountryCode .

+3


source







All Articles