RecordUnknownProps5897Test.java
package tools.jackson.databind.records;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.deser.DeserializationProblemHandler;
import tools.jackson.databind.exc.UnrecognizedPropertyException;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.*;
// [databind#5897]: fast path that skips TokenBuffer allocation for unknown
// properties must preserve observable behavior across the relevant toggles.
public class RecordUnknownProps5897Test extends DatabindTestUtil
{
record Point(int x, int y) { }
private static final String JSON_WITH_EXTRAS =
"{\"x\":1,\"extra\":{\"nested\":[1,2,3]},\"y\":2,\"trailing\":\"ignored\"}";
@Test
public void testSkipUnknowns_defaultConfig() throws Exception {
JsonMapper mapper = jsonMapperBuilder()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();
Point p = mapper.readValue(JSON_WITH_EXTRAS, Point.class);
assertEquals(1, p.x());
assertEquals(2, p.y());
}
@Test
public void testFailOnUnknown_stillThrows() throws Exception {
JsonMapper mapper = jsonMapperBuilder()
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();
assertThrows(UnrecognizedPropertyException.class,
() -> mapper.readValue(JSON_WITH_EXTRAS, Point.class));
}
@Test
public void testProblemHandler_stillInvoked() throws Exception {
final java.util.List<String> seen = new java.util.ArrayList<>();
JsonMapper mapper = jsonMapperBuilder()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.addHandler(new DeserializationProblemHandler() {
@Override
public boolean handleUnknownProperty(
tools.jackson.databind.DeserializationContext ctxt,
tools.jackson.core.JsonParser p,
tools.jackson.databind.ValueDeserializer<?> deserializer,
Object beanOrClass, String propertyName) {
seen.add(propertyName);
try { p.skipChildren(); } catch (Exception e) { throw new RuntimeException(e); }
return true;
}
})
.build();
Point p = mapper.readValue(JSON_WITH_EXTRAS, Point.class);
assertEquals(1, p.x());
assertEquals(2, p.y());
assertTrue(seen.contains("extra"), "handler saw: " + seen);
assertTrue(seen.contains("trailing"), "handler saw: " + seen);
}
// Non-final base + property-based creator + polymorphism: tokens unknown to
// the base may be known to the resolved subtype, so the TokenBuffer must be
// preserved for replay. This is the scenario that the `_beanType.isFinal()`
// guard exists to protect ��� if the fast-path ever triggered for non-final
// types, sub-only properties would be silently dropped.
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "kind")
@JsonSubTypes({ @JsonSubTypes.Type(value = Dog.class, name = "dog") })
static class Animal {
public final String name;
@JsonCreator
public Animal(@JsonProperty("name") String name) { this.name = name; }
}
static class Dog extends Animal {
public String breed;
@JsonCreator
public Dog(@JsonProperty("name") String name, @JsonProperty("breed") String breed) {
super(name);
this.breed = breed;
}
}
@Test
public void testNonFinalPolymorphic_subPropertyPreserved() throws Exception {
JsonMapper mapper = jsonMapperBuilder()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();
// "breed" appears before the type discriminator and is unknown to Animal;
// it must be buffered and replayed through Dog's deserializer.
String json = "{\"breed\":\"poodle\",\"name\":\"Rex\",\"kind\":\"dog\"}";
Animal a = mapper.readValue(json, Animal.class);
Dog dog = assertInstanceOf(Dog.class, a);
assertEquals("Rex", a.name);
assertEquals("poodle", dog.breed);
}
}