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

JacksonJsonProvider ignores DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS #189

Open
nlenoire opened this issue Jun 10, 2024 · 6 comments

Comments

@nlenoire
Copy link

nlenoire commented Jun 10, 2024

Hello,

It seems we are facing an issue with JacksonJsonProvider that silently ignores DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS set on the ObjectMapper.

The following test case reproduces the issue:

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "name", scope = Value.class)
    private static class Value {
        public String name;
        public Integer value;
    }

    private static class Owned {
        public String name;
        public Value optionalValue;

        Optional<Value> optionalValue() {
            return Optional.ofNullable(optionalValue);
        }
    }

    private static class Owner {
        public List<Owned> owned = new ArrayList<>();
        public List<Value> values = new ArrayList<>();
    }

    private final String payload = """
            {
                "owned": [
                    { "name": "foo", "optionalValue": "vFoo" },
                    { "name": "bar", "optionalValue": "this is not a valid ref to some value" },
                    { "name": "baz" },
                    { "name": "qux", "optionalValue": { "name": "vQux", "value": 3 } }
                ],
                "values": [
                    { "name": "vFoo", "value": 1 },
                    { "name": "vBar", "value": 2 }
                ]
            }
            """;

    @Test
    void should_deserialize_illegal_reference_when_configured_leniently() throws JsonProcessingException {
        final var objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS, false);
        final var owner = objectMapper.readValue(payload, Owner.class);
        assertThat(owner.owned).map(o -> o.optionalValue().map(its -> its.value).orElse(null)).containsExactly(1, null, null, 3);
    }

    @Test
    void should_reject_illegal_reference_by_default() {
        final var objectMapper = new ObjectMapper();
        assertThatExceptionOfType(JsonProcessingException.class).isThrownBy(() -> objectMapper.readValue(payload, Owner.class));
    }

    @Test
    @Disabled("jackson json provider ignores object mapper configuration so this test fails")
    void should_honor_mapper_configuration() throws IOException {
        // Stuff to use json provider (not very friendly api)
        @SuppressWarnings("unchecked")
        final Class<Object> type = (Class<Object>) (Class<?>) Owner.class;
        final var httpHeaders = new MultivaluedHashMap<String, String>();
        final var annotations = new Annotation[] {};
        final var outputStream = new ByteArrayOutputStream(4096);
        outputStream.writeBytes(payload.getBytes(StandardCharsets.UTF_8));

        // begin test
        final var objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS, true);
        final var jsonProvider = new JacksonJsonProvider(objectMapper);

        // Reverse comments in the following block to make the test pass despite the issue at hand
        try (final var inputStream = new ByteArrayInputStream(outputStream.toByteArray())) {
            assertThatExceptionOfType(JsonProcessingException.class)
                .isThrownBy(() -> jsonProvider.readFrom(type, type, annotations, MediaType.APPLICATION_JSON_TYPE, httpHeaders, inputStream));
            // final var object = jsonProvider.readFrom(type, type, annotations, MediaType.APPLICATION_JSON_TYPE, httpHeaders, inputStream);
            // final var owner = (Owner) object;
            // assertThat(owner.owned).map(o -> o.optionalValue().map(its -> its.value).orElse(null)).containsExactly(1, null, null, 3);
        }
    }

Thanks for your help.

@cowtowncoder
Copy link
Member

Does this rely on something wrt JAX-RS provider? Looks like test is trying to isolate reproduction; and if that is possible we could move this to jackson-databind and it'd be much easier to troubleshoot?
But I wanted to ask before transferring.

@nlenoire
Copy link
Author

Does this rely on something wrt JAX-RS provider? Looks like test is trying to isolate reproduction; and if that is possible we could move this to jackson-databind and it'd be much easier to troubleshoot? But I wanted to ask before transferring.

Yes, it is related to the JAX-RS provider. In a JAX-RS runtime we face this issue and the above test simulate a reproduction env. This only occurs with the JAX-RS provider, not with plain ObjectMapper.

However, if you think it's better to move it to the jackson-databind, no problem.

@cowtowncoder
Copy link
Member

Can/should only move if it reproduction is possible without JAX-RS dependencies. Sounds like it is not; if so, need to stay here.

@cowtowncoder
Copy link
Member

However... I am not really sure what could be done here -- sounds like ObjectMapper configuration problem?

@nlenoire
Copy link
Author

However... I am not really sure what could be done here -- sounds like ObjectMapper configuration problem?

@cowtowncoder I'm not sure to understand what do you mean. Arn't we injecting a properly configured ObjectMapper into a JacksonJsonProvider? Can't we expect that object mapper and its configuration to be used for deserialization?

@cowtowncoder
Copy link
Member

Ok, so this is not necessarily about FAIL_ON_UNRESOLVED_OBJECT but in general ensuring specific ObjectMapper is being used. And that would be something Provider should guarantee.

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