-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Describe the bug
When deserializing a Java Record with @JsonAnySetter
annotated field the field is left as null and the unmapped values are ignored. Given the nature of Java records, there is no other way to get the unmapped fields values (like in case of annotating a setter method).
Version information
2.13.2.2
To Reproduce
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JsonAnySetterRecordTest {
record TestRecord(
@JsonProperty String field,
@JsonAnySetter Map<String, Object> anySetter
) {}
@Test
void testJsonAnySetterOnRecord() throws JsonProcessingException {
var json = """
{
"field": "value",
"unmapped1": "value1",
"unmapped2": "value2"
}
""";
var objectMapper = new ObjectMapper();
var deserialized = objectMapper.readValue(json, TestRecord.class);
assertEquals("value", deserialized.field());
assertEquals(Map.of("unmapped1", "value1", "unmapped2", "value2"), deserialized.anySetter());
}
}
Running this test the result will be:
Expected :{unmapped1=value1, unmapped2=value2}
Actual :null
Expected behavior
The @JsonAnySetter
annotated field should contain a Map instance with all unmapped fields and their values.
Additional context
The problem happens in com.fasterxml.jackson.databind.deser.SettableAnyProperty
class in method set(...)
. The value of the @JsonAnySetter
annotated field is null and therefore setting the property value is skipped.
The suggested solution would be for Java Record to provide a new empty Map instance for the annotated field to gather the unmapped properties and this would then be provided to Record's constructor when the deserialization is concluded. Given the immutable nature of Java Records, this should ideally be some kind of immutable Map implementation (i.e. Map.copyOf(...)
?)
There might be a workaround (which however won't be probably feasible in all cases) by supplying an additional @JsonCreator
constructor or factory method, where the @JsonAnySetter
field is initialized to an empty map and then to ensure the immutability, the getter for this map needs to be overridden and the value returned wrapped as immutable.
record TestRecord(
String field,
@JsonAnySetter Map<String, Object> anySetter
) {
@JsonCreator
TestRecord(@JsonProperty("field") String field) {
this(field, new HashMap<>());
}
public Map<String, Object> anySetter() {
return Collections.unmodifiableMap(anySetter);
}
}