question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

JsonTypeInfo feature duplicates the already existing 'null'ed JsonTypeInfo#property property

See original GitHub issue

Describe the bug Instances are serialized with duplicated JSON properties.

Version information 2.13.3 and not only

To Reproduce Reproduce it by combining the following 2 aspects:

  • objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
  • com.fasterxml.jackson.databind.ObjectMapper#registerSubtypes(com.fasterxml.jackson.databind.jsontype.NamedType...)

Expected behavior The final JSON string value contains 1 JSON com.fasterxml.jackson.annotation.JsonTypeInfo#property field only as per the com.fasterxml.jackson.annotation.JsonTypeInfo’s java doc:

 *<p>
 * On serialization side, Jackson will generate type id by itself,
 * except if there is a property with name that matches
 * {@link #property()}, in which case value of that property is
 * used instead.
 *<p>

Actual behavior The final JSON string value contains 2 JSON com.fasterxml.jackson.annotation.JsonTypeInfo#property fields. One with null and another with an auto generated value.

UPDATE

Test Case:

package com.something;

import static org.junit.jupiter.api.Assertions.assertEquals;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;

/**
 * @author someone
 */
class JacksonDatabindIssues3589Test {

    @Test
    @SneakyThrows
    void demonstration() {
        final Bean bean = new Bean();
        bean.setNotIgnored("importantValue");

        final String expectedJsonValue = "{\"type\":\"beanType\",\"notIgnored\":\"importantValue\",\"ignored\":null}";
        final String actualJsonValue = createObjectMapper().writeValueAsString(bean);

        assertEquals(expectedJsonValue, actualJsonValue);
    }

    private static ObjectMapper createObjectMapper() {
        final ObjectMapper objectMapper = new ObjectMapper();

        objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        objectMapper.registerSubtypes(new NamedType(Bean.class, Bean.TYPE));

        return objectMapper;
    }

}

@Setter
@Getter
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
abstract class SuperBean {
    private String type;
}

@Setter
@Getter
class Bean extends SuperBean {
    static final String TYPE = "beanType";

    private String notIgnored;
    private String ignored;
}

Actual Result:

org.opentest4j.AssertionFailedError: 
Expected :{"type":"beanType","notIgnored":"importantValue","ignored":null}
Actual   :{"type":"beanType","type":null,"notIgnored":"importantValue","ignored":null}

Another Actual Result when adding the bean.setType("hello"); right before the existing bean.set... line:

org.opentest4j.AssertionFailedError: 
Expected :{"type":"hello","notIgnored":"importantValue","ignored":null}
Actual   :{"type":"beanType","type":"hello","notIgnored":"importantValue","ignored":null}

NOTE: The SuperBean class is not a must to be an abstract one and even the extend part is also not a must thing for issue to be valid. It persists anyway.

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
me0x847206commented, Sep 7, 2022

Added an update section with requested details.

0reactions
cowtowncodercommented, Sep 8, 2022

Yes, visible is for deserialization, but I think change to inclusion may make it unnecessary.

As to null; no, there is no logic for using “either or” – what EXISTING_PROPERTY does, essentially, is prevent TypeSerializer from writing anything. Instead a regular property is expected to be written by normal property serializer. That serializer is constrained by serialization inclusion settings. This behavior is an implementation detail, in a way, as EXISTING_PROPERTY was added long after basic @JsonTypeInfo logic (with TypeSerializer, TypeDeserializer) was implemented – so it tried to make use of existing functionality. The big challenge was (and is) that the Type Id handling is not closely integrated with data (property) handling; rather it “wraps” property handling (so TypeSerializer will delegate to JsonSerializer on writing; and TypeDeserializer to JsonDeserializer on reading).

Above is just an explanation as background: the implementation is such that EXISTING_PROPERTY will disable use of type-derived type ids on serialization.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Duplicate JSON property when `@JsonTypeInfo ... - GitHub
Consider following example which uses Jackson library: @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "kind", include = JsonTypeInfo.
Read more >
Unexpected duplicate key error using @JsonTypeInfo property
I have a simple hierarchy of data objects, which have to be converted to JSON format. Like this: @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, ...
Read more >
Using @JsonTypeInfo annotation to handle polymorphic types
In cases where polymorphic types are persisted to JSON, there's no way for Jackson to figure out the right type during deserialization.
Read more >
JsonTypeInfo (Jackson-annotations 2.4.0 API) - FasterXML
ImplClass") as JSON property "class" @JsonTypeInfo(use=Id.CLASS, ... If both exist, annotation on property has precedence, as it is considered more specific ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found