FasterXML / jackson-jaxrs-providers

Multi-module project that contains Jackson-based "old" JAX-RS (ones under `javax.ws.rs`) providers for JSON, XML, YAML, Smile, CBOR formats
Apache License 2.0
109 stars 77 forks source link

JacksonJsonProvider ignores DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS #189

Open nlenoire opened 3 weeks ago

nlenoire commented 3 weeks ago

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 commented 3 weeks ago

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 commented 3 weeks ago

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 commented 2 weeks ago

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 commented 2 weeks ago

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

nlenoire commented 2 weeks ago

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 commented 2 weeks ago

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.