RecordIgnoreGetters4157Test.java
package tools.jackson.databind.records;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.MapperFeature;
import tools.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for {@link MapperFeature#INFER_RECORD_GETTERS_FROM_COMPONENTS_ONLY}
* which controls whether only Record component getters are auto-detected
* or if all JavaBean-style getters are detected (backward compatible behavior).
*/
public class RecordIgnoreGetters4157Test extends DatabindTestUtil
{
// Test Case 1: Basic record with helper getter
record PersonRecord(String name, int age) {
// Helper method that is NOT a record component
public String getDisplayName() {
return name.toUpperCase();
}
}
// Test Case 2: Record implementing interface with getter
interface Identifiable {
String getId();
}
@JsonPropertyOrder({"name", "id"})
record UserRecord(String name) implements Identifiable {
@Override
public String getId() {
return "ID:" + name;
}
}
// Test Case 3: Record with explicit annotation on helper method
record AnnotatedHelperRecord(String name) {
@JsonProperty("display")
public String getDisplayName() {
return name.toUpperCase();
}
}
// Test Case 4: Record with is-getter helper
record BooleanHelperRecord(String name, boolean active) {
// Helper method - not a component
public boolean isSpecial() {
return name.startsWith("Special");
}
}
// Test Case 5: Record with both component getter and helper
record MixedRecord(int value) {
// Component accessor - should always work
@Override
public int value() {
return value;
}
// Helper - behavior depends on feature
public int getDoubleValue() {
return value * 2;
}
}
// Test Case 6: Empty record with static getter
record EmptyWithStatic() {
public static String getStaticValue() {
return "static";
}
}
private final ObjectMapper MAPPER_DEFAULT = newJsonMapper();
private final ObjectMapper MAPPER_RESTRICTED = jsonMapperBuilder()
.enable(MapperFeature.INFER_RECORD_GETTERS_FROM_COMPONENTS_ONLY)
.build();
/*
* Test: Feature is DISABLED by default (backward compatibility)
*/
@Test
public void testFeatureDisabledByDefault() throws Exception {
assertFalse(MAPPER_DEFAULT.isEnabled(MapperFeature.INFER_RECORD_GETTERS_FROM_COMPONENTS_ONLY));
}
/*
* Test: With feature DISABLED, helper getters ARE serialized
*/
@Test
public void testHelperGetterIncluded_FeatureDisabled() throws Exception {
PersonRecord person = new PersonRecord("john", 30);
String json = MAPPER_DEFAULT.writeValueAsString(person);
// Should include helper method
assertTrue(json.contains("displayName"), "Should include displayName with feature disabled");
assertTrue(json.contains("JOHN"), "Should have uppercase name");
assertTrue(json.contains("name"), "Should include actual component");
assertTrue(json.contains("age"), "Should include actual component");
}
/*
* Test: With feature ENABLED, helper getters are NOT serialized
*/
@Test
public void testHelperGetterExcluded_FeatureEnabled() throws Exception {
PersonRecord person = new PersonRecord("john", 30);
String json = MAPPER_RESTRICTED.writeValueAsString(person);
// Should NOT include helper method
assertFalse(json.contains("displayName"), "Should NOT include displayName with feature enabled");
assertFalse(json.contains("JOHN"), "Should NOT have uppercase name");
// Should still include actual components
assertTrue(json.contains("name"), "Should include actual component");
assertTrue(json.contains("john"), "Should have original name");
assertTrue(json.contains("age"), "Should include actual component");
}
/*
* Test: Interface getter excluded when feature enabled
*/
@Test
public void testInterfaceGetterExcluded_FeatureEnabled() throws Exception {
UserRecord user = new UserRecord("alice");
String json = MAPPER_RESTRICTED.writeValueAsString(user);
assertEquals(a2q("{'name':'alice'}"), json);
assertFalse(json.contains("id"), "Should NOT include interface getter");
}
/*
* Test: Interface getter included when feature disabled
*/
@Test
public void testInterfaceGetterIncluded_FeatureDisabled() throws Exception {
UserRecord user = new UserRecord("alice");
String json = MAPPER_DEFAULT.writeValueAsString(user);
assertTrue(json.contains("id"), "Should include interface getter");
assertTrue(json.contains("ID:alice"), "Should have computed id");
}
/*
* Test: Explicit @JsonProperty ALWAYS works regardless of feature
*/
@Test
public void testExplicitAnnotation_AlwaysWorks_FeatureEnabled() throws Exception {
AnnotatedHelperRecord record = new AnnotatedHelperRecord("test");
String json = MAPPER_RESTRICTED.writeValueAsString(record);
assertTrue(json.contains("display"), "Explicit @JsonProperty should always work");
assertTrue(json.contains("TEST"), "Should have uppercase value");
}
@Test
public void testExplicitAnnotation_AlwaysWorks_FeatureDisabled() throws Exception {
AnnotatedHelperRecord record = new AnnotatedHelperRecord("test");
String json = MAPPER_DEFAULT.writeValueAsString(record);
assertTrue(json.contains("display"), "Explicit @JsonProperty should always work");
assertTrue(json.contains("TEST"), "Should have uppercase value");
}
/*
* Test: Is-getter helpers excluded when feature enabled
*/
@Test
public void testIsGetterHelper_FeatureEnabled() throws Exception {
BooleanHelperRecord record = new BooleanHelperRecord("Special Case", true);
String json = MAPPER_RESTRICTED.writeValueAsString(record);
assertTrue(json.contains("active"), "Should include actual boolean component");
assertFalse(json.contains("special"), "Should NOT include is-getter helper");
}
@Test
public void testIsGetterHelper_FeatureDisabled() throws Exception {
BooleanHelperRecord record = new BooleanHelperRecord("Special Case", true);
String json = MAPPER_DEFAULT.writeValueAsString(record);
assertTrue(json.contains("active"), "Should include actual boolean component");
assertTrue(json.contains("special"), "Should include is-getter helper with feature disabled");
}
/*
* Test: Component accessor with same name as helper
*/
@Test
public void testComponentAccessor_AlwaysWorks() throws Exception {
MixedRecord record = new MixedRecord(42);
// Feature enabled - only component
String jsonRestricted = MAPPER_RESTRICTED.writeValueAsString(record);
assertTrue(jsonRestricted.contains("value"), "Component should be included");
assertTrue(jsonRestricted.contains("42"), "Should have value 42");
assertFalse(jsonRestricted.contains("doubleValue"), "Helper should be excluded");
// Feature disabled - both
String jsonDefault = MAPPER_DEFAULT.writeValueAsString(record);
assertTrue(jsonDefault.contains("value"), "Component should be included");
assertTrue(jsonDefault.contains("doubleValue"), "Helper should be included");
assertTrue(jsonDefault.contains("84"), "Should have doubled value");
}
/*
* Test: Round-trip with feature enabled maintains data integrity
*/
@Test
public void testRoundTrip_FeatureEnabled() throws Exception {
PersonRecord original = new PersonRecord("alice", 25);
String json = MAPPER_RESTRICTED.writeValueAsString(original);
PersonRecord deserialized = MAPPER_RESTRICTED.readValue(json, PersonRecord.class);
assertEquals(original, deserialized, "Round-trip should preserve data");
assertEquals("alice", deserialized.name());
assertEquals(25, deserialized.age());
}
/*
* Test: Deserialization ignores extra properties (helper getters not in components)
*/
@Test
public void testDeserialization_IgnoresNonComponentProperties() throws Exception {
// JSON with helper property that was serialized with feature disabled
String json = a2q("{'name':'bob','age':30,'displayName':'BOB'}");
PersonRecord deserialized = MAPPER_RESTRICTED.readValue(json, PersonRecord.class);
assertEquals("bob", deserialized.name());
assertEquals(30, deserialized.age());
// displayName is ignored during deserialization (not a component)
}
/*
* Test: Static methods are never included (baseline behavior)
*/
@Test
public void testStaticGetter_NeverIncluded() throws Exception {
EmptyWithStatic record = new EmptyWithStatic();
String jsonRestricted = MAPPER_RESTRICTED.writeValueAsString(record);
String jsonDefault = MAPPER_DEFAULT.writeValueAsString(record);
assertEquals("{}", jsonRestricted, "Static getter should not be included");
assertEquals("{}", jsonDefault, "Static getter should not be included");
}
/*
* Test: Feature configuration via builder
*/
@Test
public void testFeatureConfiguration_ViaBuilder() throws Exception {
ObjectMapper mapper = jsonMapperBuilder()
.enable(MapperFeature.INFER_RECORD_GETTERS_FROM_COMPONENTS_ONLY)
.build();
assertTrue(mapper.isEnabled(MapperFeature.INFER_RECORD_GETTERS_FROM_COMPONENTS_ONLY));
PersonRecord person = new PersonRecord("test", 1);
String json = mapper.writeValueAsString(person);
assertFalse(json.contains("displayName"));
}
/*
* Test: Feature can be disabled explicitly
*/
@Test
public void testFeatureConfiguration_ExplicitDisable() throws Exception {
ObjectMapper mapper = jsonMapperBuilder()
.disable(MapperFeature.INFER_RECORD_GETTERS_FROM_COMPONENTS_ONLY)
.build();
assertFalse(mapper.isEnabled(MapperFeature.INFER_RECORD_GETTERS_FROM_COMPONENTS_ONLY));
PersonRecord person = new PersonRecord("test", 1);
String json = mapper.writeValueAsString(person);
assertTrue(json.contains("displayName"));
}
}