I did such test.
First define a pojo:
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class ESModel {
private Long id;
@JsonUnwrapped
private Result result;
private Map<String, Object> extra = new HashMap<>();
@JsonAnyGetter
public Map<String, Object> getExtra() {
return extra;
}
@JsonAnySetter
public void set(String key, Object value) {
extra.put(key, value);
}
@Data
public static class Result {
private String name;
}
}
Then test serialization:
@Test
public void test001() {
ESModel esModel = new ESModel();
ESModel.Result child = new ESModel.Result();
child.setName("aaa");
Map<String, Object> map = new HashMap<>();
map.put("age", 12);
esModel.setId(1L);
esModel.setResult(child);
esModel.setExtra(map);
System.out.println(JSON.toJSONString(esModel));
}
The output is following which I expected.
{"id":1,"name":"aaa","age":12}
And then test deserialization:
String json = "{\"id\":1,\"name\":\"aaa\",\"age\":12}";
ESModel model = JSON.parseObject(json, ESModel.class);
System.out.println(model);
The output is following which out of my expectation. The property 'name' has been deserialized twice.
ESModel(id=1, result=ESModel.Result(name=aaa), extra={name=aaa, age=12})
PS:The JSON utitliy class is follow:
public abstract class JSON {
private static ObjectMapper objectMapper;
static {
objectMapper = new ObjectMapper();
objectMapper.setTimeZone(TimeZone.getDefault());
objectMapper.setSerializationInclusion(NON_ABSENT);
objectMapper.disable(FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.disable(READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
objectMapper.disable(WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
objectMapper.disable(DEFAULT_VIEW_INCLUSION);
objectMapper.enable(ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER);
objectMapper.enable(PROPAGATE_TRANSIENT_MARKER);
}
/*-----------------------deserialization-----------------------*/
public static <T> T parseObject(String json, Class<T> clazz) {
try {
return objectMapper.readValue(json, clazz);
} catch (IOException e) {
throw new JSONException(e.getMessage(), e);
}
}
/*-----------------------serialization-----------------------*/
public static String toJSONString(Object object) {
try {
return objectMapper.writeValueAsString(object);
} catch (IOException e) {
throw new JSONException(e.getMessage(), e);
}
}
}
Comment From: cowtowncoder
Unfortunately the way unwrapped handling is implemented there is no way for parent deserializer to know which properties it has might be handled by child deserializers, and as a result, any-setter will be given anything not consumed by deserializer itself.
With 3.0 handling can be changed and hope is this could be resolved then.
Comment From: Bijnagte
I just ran into a different flavor of this bug involving record
classes which completely fail to deserialize when both annotations are present. Order of properties does not matter. Example code:
record NameId(String id, String name) {}
@JsonInclude(JsonInclude.Include.NON_NULL)
record Nested(
@JsonUnwrapped NameId nameId,
@JsonAnySetter @JsonAnyGetter Map<String, Object> additionalProperties) {}
String json =
"""
{ "id": "12345",
"name": "Test",
"otherProperty": "value"
}
""";
ObjectMapper objectMapper = new ObjectMapper();
Nested nested = objectMapper.readValue(json, Nested.class);
throws an exception like:
java.lang.ClassCastException: Cannot construct instance of `Nested`, problem: Nested cannot be cast to class java.util.Map
Comment From: JooHyukKim
@Bijnagte Did your case Worked before, then stopped working? If so, could you provide which version behavior changed?
And have you tried the latest version out there? 2.19 or 2.20