diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java index c6dd4d1733..cb41ca9953 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java @@ -554,8 +554,10 @@ protected void _addFields(Map props) _anySetterField = new LinkedList<>(); } _anySetterField.add(f); + // 07-Feb-2025: [databind#4775]: Skip the rest of processing, but only + // for "any-setter', not any-getter + continue; } - continue; } String implName = ai.findImplicitPropertyName(f); if (implName == null) { @@ -1125,10 +1127,11 @@ protected void _addGetterMethod(Map props, _anyGetters = new LinkedList<>(); } _anyGetters.add(m); - return; + // 07-Feb-2025: [databind#4775] Do not stop processing here + // (used to return) } // @JsonKey? - if (Boolean.TRUE.equals(ai.hasAsKey(_config, m))) { + else if (Boolean.TRUE.equals(ai.hasAsKey(_config, m))) { if (_jsonKeyAccessors == null) { _jsonKeyAccessors = new LinkedList<>(); } @@ -1136,7 +1139,7 @@ protected void _addGetterMethod(Map props, return; } // @JsonValue? - if (Boolean.TRUE.equals(ai.hasAsValue(m))) { + else if (Boolean.TRUE.equals(ai.hasAsValue(m))) { if (_jsonValueAccessors == null) { _jsonValueAccessors = new LinkedList<>(); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java b/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java index c50bd0b85d..77dcdd20c6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/AnyGetterWriter.java @@ -12,12 +12,16 @@ * for serializing {@link com.fasterxml.jackson.annotation.JsonAnyGetter} annotated * (Map) properties */ -public class AnyGetterWriter +public class AnyGetterWriter extends BeanPropertyWriter + implements java.io.Serializable // since 2.19 { + // As of 2.19 + private static final long serialVersionUID = 1L; + protected final BeanProperty _property; /** - * Method (or field) that represents the "any getter" + * Method (or Field) that represents the "any getter" */ protected final AnnotatedMember _accessor; @@ -25,10 +29,14 @@ public class AnyGetterWriter protected MapSerializer _mapSerializer; + /** + * @since 2.19 + */ @SuppressWarnings("unchecked") - public AnyGetterWriter(BeanProperty property, + public AnyGetterWriter(BeanPropertyWriter parent, BeanProperty property, AnnotatedMember accessor, JsonSerializer serializer) { + super(parent); _accessor = accessor; _property = property; _serializer = (JsonSerializer) serializer; @@ -37,9 +45,20 @@ public AnyGetterWriter(BeanProperty property, } } + /** + * @deprecated Since 2.19, use one that takes {@link BeanPropertyWriter} instead. + */ + @Deprecated + public AnyGetterWriter(BeanProperty property, + AnnotatedMember accessor, JsonSerializer serializer) + { + this(null, property, accessor, serializer); + } + /** * @since 2.8.3 */ + @Override public void fixAccess(SerializationConfig config) { _accessor.fixAccess( config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); @@ -65,6 +84,11 @@ public void getAndSerialize(Object bean, JsonGenerator gen, SerializerProvider p _serializer.serialize(value, gen, provider); } + @Override + public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception { + getAndSerialize(bean, gen, prov); + } + /** * @since 2.3 */ diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java index 61add225d9..e974c093d9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializer.java @@ -147,7 +147,6 @@ protected BeanSerializerBase asArraySerializer() * - have per-property filters */ if ((_objectIdWriter == null) - && (_anyGetterWriter == null) && (_propertyFilterId == null) ) { return new BeanAsArraySerializer(this); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java index f6e5df7386..ea4e79f179 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanSerializerFactory.java @@ -26,11 +26,7 @@ import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer; import com.fasterxml.jackson.databind.ser.std.ToEmptyObjectSerializer; import com.fasterxml.jackson.databind.type.ReferenceType; -import com.fasterxml.jackson.databind.util.BeanUtil; -import com.fasterxml.jackson.databind.util.ClassUtil; -import com.fasterxml.jackson.databind.util.Converter; -import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil; -import com.fasterxml.jackson.databind.util.NativeImageUtil; +import com.fasterxml.jackson.databind.util.*; /** * Factory class that can provide serializers for any regular Java beans @@ -464,7 +460,33 @@ protected JsonSerializer constructBeanOrAddOnSerializer(SerializerProvid PropertyName name = PropertyName.construct(anyGetter.getName()); BeanProperty.Std anyProp = new BeanProperty.Std(name, valueType, null, anyGetter, PropertyMetadata.STD_OPTIONAL); - builder.setAnyGetter(new AnyGetterWriter(anyProp, anyGetter, anySer)); + + // Check if there is an accessor exposed for the anyGetter + BeanPropertyWriter anyGetterProp = null; + int anyGetterIndex = -1; + for (int i = 0; i < props.size(); i++) { + BeanPropertyWriter prop = props.get(i); + // Either any-getter as field... + if (Objects.equals(prop.getName(), anyGetter.getName()) + // or as method + || Objects.equals(prop.getMember().getMember(), anyGetter.getMember())) + { + anyGetterProp = prop; + anyGetterIndex = i; + break; + } + } + if (anyGetterIndex != -1) { + // There is prop is already in place, just need to replace it + AnyGetterWriter anyGetterWriter = new AnyGetterWriter(anyGetterProp, anyProp, anyGetter, anySer); + props.set(anyGetterIndex, anyGetterWriter); + } else { + // Otherwise just add it at the end, but won't be sorted... + // This is case where JsonAnyGetter is private/protected, + BeanPropertyDefinition anyGetterPropDef = SimpleBeanPropertyDefinition.construct(config, anyGetter, name); + BeanPropertyWriter anyPropWriter = _constructWriter(prov, anyGetterPropDef, new PropertyBuilder(config, beanDesc), staticTyping, anyGetter); + props.add(new AnyGetterWriter(anyPropWriter, anyProp, anyGetter, anySer)); + } } // Next: need to gather view information, if any: processViews(config, builder); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/BeanAsArraySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/BeanAsArraySerializer.java index 3711f9beca..36eb5ba886 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/BeanAsArraySerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/BeanAsArraySerializer.java @@ -214,10 +214,6 @@ protected final void serializeAsArray(Object bean, JsonGenerator gen, Serializer prop.serializeAsElement(bean, gen, provider); } } - // NOTE: any getters cannot be supported either - //if (_anyGetterWriter != null) { - // _anyGetterWriter.getAndSerialize(bean, gen, provider); - //} } catch (Exception e) { wrapAndThrow(provider, e, bean, props[i].getName()); } catch (StackOverflowError e) { diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/SimpleBeanPropertyFilter.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/SimpleBeanPropertyFilter.java index 574df7caab..55ad0ce288 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/SimpleBeanPropertyFilter.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/SimpleBeanPropertyFilter.java @@ -218,6 +218,8 @@ public void serializeAsField(Object pojo, JsonGenerator jgen, writer.serializeAsField(pojo, jgen, provider); } else if (!jgen.canOmitFields()) { // since 2.3 writer.serializeAsOmittedField(pojo, jgen, provider); + } else if (writer instanceof AnyGetterWriter) { + ((AnyGetterWriter) writer).getAndFilter(pojo, jgen, provider, this); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java index 7d751ab8bb..4c63e00df3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/BeanSerializerBase.java @@ -65,12 +65,6 @@ public abstract class BeanSerializerBase */ final protected BeanPropertyWriter[] _filteredProps; - /** - * Handler for {@link com.fasterxml.jackson.annotation.JsonAnyGetter} - * annotated properties - */ - final protected AnyGetterWriter _anyGetterWriter; - /** * Id of the bean property filter to use, if any; null if none. */ @@ -118,13 +112,11 @@ protected BeanSerializerBase(JavaType type, BeanSerializerBuilder builder, // 20-Sep-2019, tatu: Actually not just that but also "dummy" serializer for // case of no bean properties, too _typeId = null; - _anyGetterWriter = null; _propertyFilterId = null; _objectIdWriter = null; _serializationShape = null; } else { _typeId = builder.getTypeId(); - _anyGetterWriter = builder.getAnyGetter(); _propertyFilterId = builder.getFilterId(); _objectIdWriter = builder.getObjectIdWriter(); final JsonFormat.Value format = builder.getBeanDescription().findExpectedFormat(); @@ -141,7 +133,6 @@ protected BeanSerializerBase(BeanSerializerBase src, _filteredProps = filteredProperties; _typeId = src._typeId; - _anyGetterWriter = src._anyGetterWriter; _objectIdWriter = src._objectIdWriter; _propertyFilterId = src._propertyFilterId; _serializationShape = src._serializationShape; @@ -165,7 +156,6 @@ protected BeanSerializerBase(BeanSerializerBase src, _filteredProps = src._filteredProps; _typeId = src._typeId; - _anyGetterWriter = src._anyGetterWriter; _objectIdWriter = objectIdWriter; _propertyFilterId = filterId; _serializationShape = src._serializationShape; @@ -209,7 +199,6 @@ protected BeanSerializerBase(BeanSerializerBase src, Set toIgnore, Set products; + @JsonUnwrapped + public Location childEntities; + } + + // @JsonAnyGetter, with different property order + @JsonPropertyOrder({"childEntities", "entityId", "totalTests", "entityName", "products"}) + static class PojoPropertyVersion1 extends BaseWithProperties { + } + + // @JsonAnyGetter, with different property order + @JsonPropertyOrder({"entityId", "totalTests", "childEntities", "products", "entityName"}) + static class PojoPropertyVersion2 extends BaseWithProperties { + } + + // @JsonUnwrapped, with different property order + @JsonPropertyOrder({"childEntities", "entityId", "totalTests", "entityName", "products"}) + static class PojoUnwrappedVersion1 extends BaseWithProperties { + } + + // @JsonUnwrapped, with different property order + @JsonPropertyOrder({"entityId", "totalTests", "childEntities", "entityName", "products"}) + static class PojoUnwrappedVersion2 extends BaseWithProperties { + } + + static class Location { + public int child1; + public int child2; + } + + @JsonIgnoreProperties("b") + static class IgnorePropertiesOnFieldPojo { + public int a = 1, b = 2; + @JsonAnyGetter + public Map map = new HashMap<>(); + } + + static class IgnorePropertiesOnAnyGetterPojo { + public int a = 1, b = 2; + @JsonIgnoreProperties("b") + @JsonAnyGetter + public Map map = new HashMap<>(); + } + + static class IgnoreOnFieldPojo { + public int a = 1; + @JsonIgnore + public int b = 2; + @JsonAnyGetter + public Map map = new HashMap<>(); + } + + static class AlphabeticOrderOnAnyGetterBean { + @JsonPropertyOrder(alphabetic = true) + @JsonAnyGetter + public Map map = new LinkedHashMap<>(); + } + + @JsonPropertyOrder(alphabetic = true) + static class AlphabeticOrderOnClassBean { + public int c = 3, a = 1, b = 2; + @JsonAnyGetter + public Map map = new LinkedHashMap<>(); + } + + static class LinkUnlinkConflictPojo { + private Map properties = new HashMap<>(); + + @JsonAnyGetter + public Map getProperties() { + properties.put("key", "value"); + return properties; + } + + @JsonIgnore + public String getProperties(String key) { + // This method is unrelated to the any-getter and should not affect serialization + return "unrelated"; + } + + @JsonIgnore + public String getKey() { + // This method is unrelated to the any-getter and should not affect serialization + return "unrelated"; + } + } + + @JsonPropertyOrder({ "firstProperty", "secondProperties", "thirdProperty", "forthProperty" }) + static class PrivateAnyGetterPojo { + public int firstProperty = 1, forthProperty = 4, thirdProperty = 3; + + @JsonAnyGetter + private Map secondProperties = new HashMap<>(); + + public PrivateAnyGetterPojo add(String key, Object value) { + secondProperties.put(key, value); + return this; + } + + public Map secondProperties() { + return secondProperties; + } + } + + @JsonPropertyOrder({ "firstProperty", "secondProperties", "thirdProperty", "forthProperty" }) + static class PrivateAnyGetterPojoSorted extends PrivateAnyGetterPojo { + public Map getSecondProperties() { + return super.secondProperties; + } + } + + private final ObjectMapper MAPPER = newJsonMapper(); + + // For [databind#4388] + @Test + public void testSerializationOrderVersion1() throws Exception { + PojoPropertyVersion1 input = new PojoPropertyVersion1(); + _configureValues(input); + + String json = MAPPER.writeValueAsString(input); + + assertEquals(a2q("{" + + "'child1':3," + + "'child2':3," + + "'entityId':1," + + "'totalTests':2," + + "'entityName':'Bob'," + + "'product1':4}"), + json); + } + + @Test + public void testSerializationOrderVersion2() throws Exception { + PojoPropertyVersion2 input = new PojoPropertyVersion2(); + _configureValues(input); + + String json = MAPPER.writeValueAsString(input); + + assertEquals(a2q("{" + + "'entityId':1," + + "'totalTests':2," + + "'child1':3," + + "'child2':3," + + "'product1':4," + + "'entityName':'Bob'}"), + json); + } + + @Test + public void testSerializationOrderUnwrappedVersion1() throws Exception { + PojoUnwrappedVersion1 input = new PojoUnwrappedVersion1(); + _configureValues(input); + + String json = MAPPER.writeValueAsString(input); + + assertEquals(a2q("{" + + "'child1':3," + + "'child2':3," + + "'entityId':1," + + "'totalTests':2," + + "'entityName':'Bob'," + + "'product1':4}"), + json); + } + + @Test + public void testSerializationOrderUnwrappedVersion2() throws Exception { + PojoUnwrappedVersion2 input = new PojoUnwrappedVersion2(); + _configureValues(input); + + String json = MAPPER.writeValueAsString(input); + + assertEquals(a2q("{" + + "'entityId':1," + + "'totalTests':2," + + "'child1':3," + + "'child2':3," + + "'entityName':'Bob'," + + "'product1':4}"), + json); + } + + @Test + public void testIgnoreProperties() throws Exception { + // Respsect @JsonIgnoreProperties 'b' from Pojo, but not from map + IgnorePropertiesOnFieldPojo bean = new IgnorePropertiesOnFieldPojo(); + bean.map.put("b", 3); + assertEquals(a2q("{'a':1,'b':3}"), MAPPER.writeValueAsString(bean)); + + // Respect @JsonIgnoreProperties 'b' from Pojo, but not from map + IgnorePropertiesOnAnyGetterPojo bean2 = new IgnorePropertiesOnAnyGetterPojo(); + bean2.map.put("b", 3); + assertEquals(a2q("{'a':1,'b':2}"), MAPPER.writeValueAsString(bean2)); + + // Respect @JsonIgnore from Pojo, but not from map + IgnoreOnFieldPojo bean3 = new IgnoreOnFieldPojo(); + bean3.map.put("b", 3); + assertEquals(a2q("{'a':1,'b':3}"), MAPPER.writeValueAsString(bean3)); + } + + // Sorting works on @JsonAnyGetter, when adding @JsonPropertyOrder directly on the AnyGetter method + @Test + public void testSortingOnAnyGetter() throws Exception { + AlphabeticOrderOnAnyGetterBean bean = new AlphabeticOrderOnAnyGetterBean(); + bean.map.put("zd", 4); + bean.map.put("zc", 3); + bean.map.put("za", 1); + bean.map.put("zb", 2); + assertEquals(a2q("{" + + "'za':1," + + "'zb':2," + + "'zc':3," + + "'zd':4}"), MAPPER.writeValueAsString(bean)); + } + + // Sorting does not work on @JsonAnyGetter, when adding @JsonPropertyOrder on the class + @Test + public void testSortingOnClassNotPropagateToAnyGetter() throws Exception { + AlphabeticOrderOnClassBean bean = new AlphabeticOrderOnClassBean(); + bean.map.put("zc", 3); + bean.map.put("za", 1); + bean.map.put("zb", 2); + assertEquals(a2q("{" + + "'a':1," + + "'b':2," + + "'c':3," + + "'zc':3," + + "'za':1," + + "'zb':2}"), MAPPER.writeValueAsString(bean)); + } + + @Test + public void testLinkUnlinkWithJsonIgnore() throws Exception { + LinkUnlinkConflictPojo pojo = new LinkUnlinkConflictPojo(); + String json = MAPPER.writeValueAsString(pojo); + + assertEquals(a2q("{'key':'value'}"), json); + } + + + @Test + public void testPrivateAnyGetter() throws Exception { + PrivateAnyGetterPojo pojo = new PrivateAnyGetterPojo(); + pojo.add("secondProperty", 2); + String json = MAPPER.writeValueAsString(pojo); + + assertEquals(a2q("{" + + "'firstProperty':1," + + "'thirdProperty':3," + + "'forthProperty':4," + + "'secondProperty':2}"), // private accesor, wont' work here + json); + } + + @Test + public void testPrivateAnyGetterSorted() throws Exception { + PrivateAnyGetterPojoSorted pojo = new PrivateAnyGetterPojoSorted(); + pojo.add("secondProperty", 2); + String json = MAPPER.writeValueAsString(pojo); + + assertEquals(a2q("{" + + "'firstProperty':1," + + "'secondProperty':2," + // private accesor, wont' work here + "'thirdProperty':3," + + "'forthProperty':4}"), + json); + } + + private void _configureValues(BaseWithProperties base) { + base.entityId = 1; + base.entityName = "Bob"; + base.totalTests = 2; + base.childEntities = new Location(); + base.childEntities.child1 = 3; + base.childEntities.child2 = 3; + base.products = new HashMap<>(); + base.products.put("product1", 4); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterTest.java index 8681f80281..b91746e1ea 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/AnyGetterTest.java @@ -186,6 +186,24 @@ public void set(String name, String value) { } } + // [databind#1458]: Allow `@JsonAnyGetter` on fields too + static class DynaFieldOrderedBean { + public int id = 123; + + @JsonPropertyOrder(alphabetic = true) + @JsonAnyGetter + @JsonAnySetter + private HashMap other = new LinkedHashMap<>(); + + public Map any() { + return other; + } + + public void set(String name, String value) { + other.put(name, value); + } + } + // [databind#1458]: Allow `@JsonAnyGetter` on fields too @Test public void testDynaFieldBean() throws Exception @@ -200,6 +218,18 @@ public void testDynaFieldBean() throws Exception assertEquals("Joe", result.other.get("name")); } + // [databind#4388]: Allow `@JsonPropertyOrder` AND `@JsonAnyGetter` on fields too + @Test + public void testDynaFieldOrderedBean() throws Exception + { + DynaFieldOrderedBean b = new DynaFieldOrderedBean(); + b.set("nameC", "Cilly"); + b.set("nameB", "Billy"); + b.set("nameA", "Ailly"); + + assertEquals("{\"id\":123,\"nameA\":\"Ailly\",\"nameB\":\"Billy\",\"nameC\":\"Cilly\"}", MAPPER.writeValueAsString(b)); + } + /* /********************************************************** /* Test methods @@ -300,8 +330,7 @@ public void testAnyGetterWithMapperDefaultIncludeNonEmptyAndFilterOnBean() throw input.add("empty", ""); input.add("null", null); String json = mapper.writeValueAsString(input); - // Unfortunately path for bean with filter is different. It still skips nulls. - assertEquals("{\"non-empty\":\"property\",\"empty\":\"\"}", json); + assertEquals("{\"non-empty\":\"property\"}", json); } // [databind#2592] diff --git a/src/test/java/com/fasterxml/jackson/databind/tofix/ShapeArrayWithAnyGetter4961Test.java b/src/test/java/com/fasterxml/jackson/databind/tofix/ShapeArrayWithAnyGetter4961Test.java new file mode 100644 index 0000000000..f4804ec31b --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/tofix/ShapeArrayWithAnyGetter4961Test.java @@ -0,0 +1,84 @@ +package com.fasterxml.jackson.databind.tofix; + +import java.util.Map; +import java.util.TreeMap; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.testutil.DatabindTestUtil; +import com.fasterxml.jackson.databind.testutil.failure.JacksonTestFailureExpected; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// [databind#4961] Serialization for `JsonFormat.Shape.ARRAY` does not work when there is `@JsonAnyGetter` +public class ShapeArrayWithAnyGetter4961Test + extends DatabindTestUtil +{ + + static class WrapperForAnyGetter { + public BeanWithAnyGetter value; + } + + @JsonPropertyOrder({ "firstProperty", "secondProperties", "anyProperty", "forthProperty" }) + @JsonFormat(shape = JsonFormat.Shape.ARRAY) + static class BeanWithAnyGetter { + public String firstProperty = "first"; + public String secondProperties = "second"; + public String forthProperty = "forth"; + @JsonAnyGetter + public Map getAnyProperty() { + Map map = new TreeMap<>(); + map.put("third_A", "third_A"); + map.put("third_B", "third_B"); + return map; + } + } + + final ObjectMapper MAPPER = newJsonMapper(); + + @JacksonTestFailureExpected + @Test + public void testSerializeArrayWithAnyGetterWithWrapper() throws Exception { + WrapperForAnyGetter wrapper = new WrapperForAnyGetter(); + wrapper.value = new BeanWithAnyGetter(); + + String json = MAPPER.writeValueAsString(wrapper); + + // In 2.19, Fails Actual + // : {"value":{"firstProperty":"first","secondProperties":"second","forthProperty":"forth","third_A":"third_A","third_B":"third_B"}} + // Getting better, after #4775 in 2.19, fails Actual + // : {"value":["first","second",{"third_A":"third_A","third_B":"third_B"},"forth"]} + assertEquals(a2q("{'value':" + + "[" + + "'first'," + + "'second'," + + "'forth'," + + "'third_A'," + + "'third_B'" + + "]" + + "}"), json); + } + + @JacksonTestFailureExpected + @Test + public void testSerializeArrayWithAnyGetterAsRoot() throws Exception { + BeanWithAnyGetter bean = new BeanWithAnyGetter(); + + String json = MAPPER.writeValueAsString(bean); + + // In 2.19, Fails Actual + // : {"firstProperty":"first","secondProperties":"second","forthProperty":"forth","third_A":"third_A","third_B":"third_B"} + + // Getting better, after #4775 in 2.19, fails Actual + // : ["first","second",{"third_A":"third_A","third_B":"third_B"},"forth"] + assertEquals(a2q("[" + + "'first'," + + "'second'," + + "'forth'," + + "'third_A'," + + "'third_B'" + + "]"), json); + } +}