Skip to content

Add MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_OPTIONALS to prevent failure of java.util.Optional (de)serialization without Java 8 module #5006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
cowtowncoder opened this issue Mar 6, 2025 · 3 comments

Comments

@cowtowncoder
Copy link
Member

(note: follow-up to #4499)

So: looks like #4082 has caused many problems and although direction is clear wrt canonical (de)serialization of Optionals (basically what https://github.com/FasterXML/jackson-modules-java8/ adds), it seems reasonably to add a MapperFeature that basically disables checks #4082 adds.

What this means is that when the new MapperFeature (Name To Be Decided) is changed:

  1. java.util.Optional handling proceeds the way it would with no checks -- specifically, it is fine if default Bean-style handling occurs
  2. No exception is thrown for missing explicit java.util.Optional handlers

It may seem odd to add a "feature to disable another newish feature", but this seems minimal path that achieves goals of #4082 while still allowing backwards-compatibility for users that depended on old handling (esp. with explicit visibility changes).

@cowtowncoder cowtowncoder changed the title Add 2.x MapperFeature to prevent failure of java.util.Optional (de)serialization without Java 8 module Add MapperFeature.REQUIRE_HANDLERS_FOR_JAVA8_OPTIONALS to prevent failure of java.util.Optional (de)serialization without Java 8 module Mar 13, 2025
@seadbrane
Copy link

HI -
This is doesn't appear to be working - or at least not as I expected. While this no longer fails when deserializing an Optional, an Optional serialized in the legacy format { "value": "value", "present": true } is only deserialized back to an empty Optional.

@cowtowncoder
Copy link
Member Author

@seadbrane I don't know what would be different there -- new setting simply prevents failure, affecting no other handling. Possibly some other changes, unrelated to this issue -- Property discovery rewrite of 2.18 -- ?

@seadbrane
Copy link

Is there a simpler way to write a deserializer that still supports the old behavior than the following? My main concern is whether this covers all use cases, especially since I had to add custom logic to handle collection types.

    public static class OptionalDeserializer<T> extends StdDeserializer<Optional<T>> implements ContextualDeserializer {

        private JavaType type;

        public OptionalDeserializer() {
            super(Optional.class);
        }

        private OptionalDeserializer(final JavaType type) {
            super(Optional.class);
            this.type = type;
        }

        @Override
        public JsonDeserializer<?> createContextual(final DeserializationContext ctxt, final BeanProperty property) {
            final JavaType wrapperType = property.getType().containedType(0);
            return new OptionalDeserializer(wrapperType);

        }

        @Override
        public Optional<T> deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
            if (p.getCurrentToken() == JsonToken.VALUE_NULL) {
                return Optional.empty();

            }

            final JsonNode node = p.getCodec().readTree(p);
            final JsonNode valueNode = node.get("value");

            if (null != valueNode && !valueNode.isNull()) {
                final JavaType valueType;
                if (this.type == null) {
                    valueType = ctxt.getContextualType();
                } else {
                    valueType = this.type;
                }
                if (valueType.isCollectionLikeType() && valueNode.isArray()) {
                    // final Class<T> rawClass = (Class<T>) valueType.getRawClass();
                    final Collection collection = Set.class
                            .isAssignableFrom(valueType.getRawClass())
                            ? new HashSet<>()
                                    : new ArrayList<>();

                    final JavaType elementType = valueType.getBindings().getBoundType(0);

                    for (final JsonNode elementNode : (ArrayNode) valueNode) {
                        collection.add(createValue(p, elementType, elementNode));
                    }
                    return Optional.of((T) collection);
                }
                return Optional.ofNullable(createValue(p, valueType, valueNode));
            }
            return Optional.empty();
        }

        private <T> T createValue(JsonParser p, JavaType valueType, JsonNode valueNode) throws JsonProcessingException {
            final Class<T> rawClass = (Class<T>) valueType.getRawClass();
            final T value = p.getCodec().treeToValue(valueNode, rawClass);
            return value;
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants