Skip to content
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

Optional deserialization, serialization ignore contentConverter #294

Closed
richardsonwk opened this issue Dec 8, 2023 · 1 comment
Closed
Labels
2.17 has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue
Milestone

Comments

@richardsonwk
Copy link

I was surprised to find that contentConverter does not work with Optional, but contentUsing does. I think this is effectively the same as #97.

    /**
     * Pretend like this class cannot be annotated.
     */
    public static final class StringWrapper {
        private final String mWrapped;

        public StringWrapper(final String s) {
            mWrapped = s;
        }

        @Override
        public boolean equals(final Object other) {
            if (other instanceof StringWrapper) {
                return mWrapped.equals(((StringWrapper) other).mWrapped);
            }

            return false;
        }

        public String get() {
            return mWrapped;
        }
    }

    /**
     * A class in my library.
     */
    public static final class SomeClassToSerialize {
        @JsonDeserialize(contentConverter = StringWrapperIn.class)
        @JsonSerialize(contentConverter = StringWrapperOut.class)
        @JsonProperty("field")
        private final Optional<StringWrapper> mField;

        /**
         * For Jackson reading only.
         */
        private SomeClassToSerialize() {
            mField = null;
        }

        public SomeClassToSerialize(final Optional<StringWrapper> value) {
            mField = value;
        }

        @Override
        public boolean equals(final Object other) {
            if (other instanceof SomeClassToSerialize) {
                return mField.equals(((SomeClassToSerialize) other).mField);
            }

            return false;
        }
    }

    public static final class StringWrapperIn extends StdConverter<String, StringWrapper> {
        @Override
        public StringWrapper convert(final String value) {
            System.out.println("Working on the way in");
            return new StringWrapper(value);
        }
    }

    public static final class StringWrapperOut extends StdConverter<StringWrapper, String> {
        @Override
        public String convert(final StringWrapper value) {
            System.out.println("Working on the way out");
            return value.get();
        }
    }

    public static void main(final String[] args) throws JsonProcessingException {
        final SomeClassToSerialize original =
            new SomeClassToSerialize(Optional.of(new StringWrapper("value")));

        final ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new Jdk8Module());

        final String out =
            mapper.writer()
                .with(INDENT_OUTPUT)
                .writeValueAsString(original);

        System.out.println(out);

        final SomeClassToSerialize in =
            mapper.readValue(out, SomeClassToSerialize.class);

        assert original.equals(in);
    }

The output is

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class StringWrapper and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
...

A slight tweak to instead specify a content serializer fixes it:

    public static final class SomeClassToSerialize {
        @JsonDeserialize(contentUsing = StringWrapperDeserializer.class)
        @JsonSerialize(contentUsing = StringWrapperSerializer.class)
        @JsonProperty("field")
        private final Optional<StringWrapper> mField;
        ...
    }

    public static final class StringWrapperDeserializer extends StdDelegatingDeserializer<StringWrapper> {
        public StringWrapperDeserializer() {
            super(new StringWrapperIn());
        }

        /*
         * Somewhat unrelated, I don't follow why this override is necessary, since it does exactly what the superclass
         * does - except for throwing for any subclass.
         */
        @Override
        protected StdDelegatingDeserializer<StringWrapper> withDelegate(
            final Converter<Object, StringWrapper> converter,
            final JavaType delegateType,
            final JsonDeserializer<?> delegateDeserializer) {

            return new StdDelegatingDeserializer<>(converter, delegateType, delegateDeserializer);
        }
    }

    public static final class StringWrapperSerializer extends StdDelegatingSerializer {
        public StringWrapperSerializer() {
            super(new StringWrapperOut());
        }
    }

The output is then

Working on the way out
{
  "field" : "value"
}
Working on the way in

This is not a difficult workaround, so it's no problem - just unexpected.

@cowtowncoder
Copy link
Member

Thank you for reporting this @richardsonwk -- it definitely sounds like bug and is not intentional.

@cowtowncoder cowtowncoder added the has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue label Dec 18, 2023
@cowtowncoder cowtowncoder changed the title Optional serialization ignores contentConverter but not contentUsing Optional deserialization, serialization ignore contentConverter Jan 24, 2024
@cowtowncoder cowtowncoder added this to the 2.17.0 milestone Jan 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.17 has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue
Projects
None yet
Development

No branches or pull requests

2 participants