Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC @RequestBody map Optional<Enum>

I have a rest controller with 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 has a field isMeetingOrSale that is enum (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 map a json that has a field "isMeetingOrSale" : "MEETING"

but the value in the json can be "isMeetingOrSale" : "" or completely missing, so in that case I want the field to be mapped to null. If I change the filed to be Optional<MeetingSaleFlag>

I got

Could not read JSON: Can not instantiate value of type [simple type, class java.util.Optional<MeetingSaleFlag>] from String value ('MEETING'); no single-String constructor/factory method\\n at [Source: java.io.PushbackInputStream@32b21158; line: 17, column: 18] (through reference chain: MyModel[\"isMeetingOrSale\"]);

So the question is how can I map Optional enum from json?

like image 889
Evgeni Dimitrov Avatar asked Jul 02 '26 19:07

Evgeni Dimitrov


2 Answers

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

1) Add

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

as a dependency.

2) Reconfigure the Jackson mapper. Register:

    @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 of type com.fasterxml.jackson.databind.Module to your context. They will be registered with every bean of type ObjectMapper, providing a global mechanism for contributing custom modules when you add new features to your application.

Doing this will only register the additional module and keep the built-in Jackson configuration provided by Spring Boot.

3) result

Now when the property is missing from the sent json, it's mapped to null (This is not that great. I was expecting that it will give me an Optional and I will be able to use .isPresent()). When it's an empty string ("isMeetingOrSale" : ""), Jackson returns an error:

Could not read JSON: Can not construct instance of MyModel from String value '': value not one of declared Enum instance names: [VAL1, VAL2]

which looks OK to me.

Useful links : Jackson jdk8 module, Spring MVC configure Jackson

like image 178
Evgeni Dimitrov Avatar answered Jul 04 '26 09:07

Evgeni Dimitrov


This is an example from 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)serialized 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 replicate the same scenario using MeetingSaleFlag instead of CountryCode.

like image 29
m c Avatar answered Jul 04 '26 09:07

m c