JsonTypeInfo feature duplicates the already existing 'null'ed JsonTypeInfo#property property
See original GitHub issueDescribe 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:
- Created a year ago
- Comments:5 (3 by maintainers)
Top GitHub Comments
Added an update section with requested details.
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” – whatEXISTING_PROPERTY
does, essentially, is preventTypeSerializer
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, asEXISTING_PROPERTY
was added long after basic@JsonTypeInfo
logic (withTypeSerializer
,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 (soTypeSerializer
will delegate toJsonSerializer
on writing; andTypeDeserializer
toJsonDeserializer
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.