Hi,
I'm working on a deserializer for Optionals with specifiable values for 
Optional.empty().

Here is the source code of the deserializer I came up with:
public class EmptyOptionalDeserializer extends JsonDeserializer<Optional<?>> 
implements ContextualDeserializer {
    private Map<Class<?>, Set<Object>> emptyValues;
    private JavaType valueType;


    public EmptyOptionalDeserializer() {
        this(Collections.synchronizedMap(new HashMap<>()), null);
    }


    /**
     * This constructor is only used for the contextual deserialization.
     * @param emptyValues Reference to the empty values map.
     * @param valueType Optional's value type.
     */
    protected EmptyOptionalDeserializer(Map<Class<?>, Set<Object>> 
emptyValues, JavaType valueType) {
        this.emptyValues = emptyValues;
        this.valueType = valueType;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, 
BeanProperty property) throws JsonMappingException {
        return new EmptyOptionalDeserializer(emptyValues, property.getType
().containedType(0));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Optional<?> deserialize(JsonParser parser, DeserializationContext 
ctxt) throws IOException, JsonProcessingException {
        Object value = ctxt.readValue(parser, valueType);

        if(value == null) {
            return Optional.empty();
        }


        if(!emptyValues.containsKey(valueType.getRawClass())) { // class 
not registered
            return Optional.of(value);
        }


        Set<Object> emptyValues = this.emptyValues.get(valueType.getRawClass
());


        if(emptyValues == null) {
            return Optional.of(value);
        }


        return emptyValues.contains(value) ? Optional.empty() : Optional.of(
value);
    }


    /**
     * Registers values for a type which will count as empty optional.
     * @param type Type to register empty values for.
     * @param emptyValues Empty values.
     * @return Returns itself for method chaining.
     */
    public EmptyOptionalDeserializer register(Class<?> type, Object... 
emptyValues) {
        Collections.addAll(this.emptyValues.computeIfAbsent(type, t -> new 
HashSet<>()), emptyValues);
        return this;
    }
}

This is how I create the ObjectMapper:
new ObjectMapper()
    .registerModule(new JavaTimeModule())
    .registerModule(
        new SimpleModule()
            .addDeserializer(
                Optional.class,
                new EmptyOptionalDeserializer()
                    .register(Integer.class, new Integer(-1))
                        .register(String.class, "")
            )
    );

It works as expected for "real" values, but if null occurres it does not.

For example if I have this JSON string:
{"test":null}

And this class:
public class A { public Optional<String> test; }

And want to deserialize:
A a = mapper.readValue(json, A.class);

I expected a.test to be an empty Optional now, but it is actually null.

Looking a bit deeper into it, I can see that 
EmptyOptionalDeserializer#createContextual will be called and thus the 
actual deserializer is instantiated. And now the problem arises: the 
deserializer's deserialze method will not be called, and a.test will be set 
to null by something else, so a work around like public class A { public 
Optional<String> test = Optional.empty(); } does not work either.

If i run the same code against a JSON string like this:
{"test":""}
It is perfectly fine and a.test is an empty Optional.

What am I doing wrong?

Regards,
cap5lut

-- 
You received this message because you are subscribed to the Google Groups 
"jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to jackson-user+unsubscr...@googlegroups.com.
To post to this group, send email to jackson-user@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to