AnyGetterTest.java

package tools.jackson.databind.ser;

import java.util.*;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;

import tools.jackson.core.JsonGenerator;
import tools.jackson.core.json.JsonWriteFeature;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.node.ObjectNode;
import tools.jackson.databind.ser.std.*;
import tools.jackson.databind.testutil.DatabindTestUtil;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

public class AnyGetterTest extends DatabindTestUtil
{
    static class Bean
    {
        final static Map<String,Boolean> extra = new HashMap<String,Boolean>();
        static {
            extra.put("a", Boolean.TRUE);
        }

        public int getX() { return 3; }

        @JsonAnyGetter
        public Map<String,Boolean> getExtra() { return extra; }
    }

    static class AnyOnlyBean
    {
        @JsonAnyGetter
        public Map<String,Integer> any() {
            HashMap<String,Integer> map = new HashMap<String,Integer>();
            map.put("a", 3);
            return map;
        }
    }

    // For [databind#1376]: allow disabling any-getter
    static class NotEvenAnyBean extends AnyOnlyBean
    {
        @JsonAnyGetter(enabled=false)
        @Override
        public Map<String,Integer> any() {
            throw new RuntimeException("Should not get called!)");
        }

        public int getValue() { return 42; }
    }

    static class MapAsAny
    {
        protected Map<String,Object> stuff = new LinkedHashMap<String,Object>();

        @JsonAnyGetter
        public Map<String,Object> any() {
            return stuff;
        }

        public void add(String key, Object value) {
            stuff.put(key, value);
        }
    }

    static class Issue705Bean
    {
        protected Map<String,String> stuff;

        public Issue705Bean(String key, String value) {
            stuff = new LinkedHashMap<String,String>();
            stuff.put(key, value);
        }

        @JsonSerialize(using = Issue705Serializer.class)
        @JsonAnyGetter
        public Map<String, String> getParameters(){
            return stuff;
        }
    }

    static class Issue705Serializer extends StdSerializer<Object>
    {
        public Issue705Serializer() {
            super(Map.class);
        }

        @Override
        public void serialize(Object value, JsonGenerator g, SerializationContext ctxt)
        {
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<?,?> entry : ((Map<?,?>) value).entrySet()) {
                sb.append('[').append(entry.getKey()).append('/').append(entry.getValue()).append(']');
            }
            g.writeStringProperty("stuff", sb.toString());
        }
    }

    // [databind#1124]
    static class Bean1124
    {
        protected Map<String,String> additionalProperties;

        public void addAdditionalProperty(String key, String value) {
            if (additionalProperties == null) {
                additionalProperties = new HashMap<String,String>();
            }
            additionalProperties.put(key,value);
        }

        public void setAdditionalProperties(Map<String, String> additionalProperties) {
            this.additionalProperties = additionalProperties;
        }

        @JsonAnyGetter
        @JsonSerialize(contentUsing=MyUCSerializer.class)
        public Map<String,String> getAdditionalProperties() { return additionalProperties; }
    }

    // [databind#1124]
    static class MyUCSerializer extends StdScalarSerializer<String>
    {
        public MyUCSerializer() { super(String.class); }

        @Override
        public void serialize(String value, JsonGenerator gen,
                SerializationContext provider) {
            gen.writeString(value.toUpperCase());
        }
    }

    static class Bean2592NoAnnotations
    {
        protected Map<String, String> properties = new LinkedHashMap<>();

        @JsonAnyGetter
        public Map<String, String> getProperties() {
            return properties;
        }

        public void setProperties(Map<String, String> properties) {
            this.properties = properties;
        }

        public void add(String key, String value) {
            properties.put(key, value);
        }
    }

    static class Bean2592PropertyIncludeNonEmpty extends Bean2592NoAnnotations
    {
        @JsonInclude(content = JsonInclude.Include.NON_EMPTY)
        @JsonAnyGetter
        @Override
        public Map<String, String> getProperties() {
            return properties;
        }
    }

    @JsonFilter("Bean2592")
    static class Bean2592WithFilter extends Bean2592NoAnnotations {}

    // [databind#1458]: Allow `@JsonAnyGetter` on fields too
    static class DynaFieldBean {
        public int id;

        @JsonAnyGetter
        @JsonAnySetter
        protected HashMap<String,String> other = new HashMap<String,String>();

        public Map<String,String> any() {
            return other;
        }

        public void set(String name, String value) {
            other.put(name, value);
        }
    }

    // [databind#1458]: Allow `@JsonAnyGetter` on fields too
    static class DynaFieldOrderedBean {
        public int id = 123;

        @JsonPropertyOrder(alphabetic = true)
        @JsonAnyGetter
        @JsonAnySetter
        private HashMap<String,String> other = new LinkedHashMap<>();

        public Map<String,String> any() {
            return other;
        }

        public void set(String name, String value) {
            other.put(name, value);
        }
    }

    // For [databind#518]
    @JsonPropertyOrder(alphabetic = true)
    static class Bean518
    {
        public int b;
        protected Map<String,Object> extra = new HashMap<>();
        public int a;

        public Bean518(int a, int b, Map<String,Object> x) {
            this.a = a;
            this.b = b;
            extra = x;
        }

        @JsonAnyGetter
        public Map<String,Object> getExtra() { return extra; }
    }

    // For [databind#4388]
    static class BaseWithProperties {
        public String entityName;
        public int entityId;
        public Integer totalTests;
        @JsonAnyGetter
        public Map<String, Object> products;
        @JsonUnwrapped
        public Location childEntities;
    }

    @JsonPropertyOrder({"childEntities", "entityId", "totalTests", "entityName", "products"})
    static class PojoPropertyVersion1 extends BaseWithProperties { }

    @JsonPropertyOrder({"entityId", "totalTests", "childEntities", "products", "entityName"})
    static class PojoPropertyVersion2 extends BaseWithProperties { }

    @JsonPropertyOrder({"childEntities", "entityId", "totalTests", "entityName", "products"})
    static class PojoUnwrappedVersion1 extends BaseWithProperties { }

    @JsonPropertyOrder({"entityId", "totalTests", "childEntities", "entityName", "products"})
    static class PojoUnwrappedVersion2 extends BaseWithProperties { }

    @JsonPropertyOrder({"child1", "child2"})
    static class Location {
        public int child1;
        public int child2;
    }

    @JsonIgnoreProperties("b")
    static class IgnorePropertiesOnFieldPojo {
        public int a = 1, b = 2;
        @JsonAnyGetter
        public Map<String, Object> map = new HashMap<>();
    }

    @JsonPropertyOrder({"a", "b"})
    static class IgnorePropertiesOnAnyGetterPojo {
        public int a = 1, b = 2;
        @JsonIgnoreProperties("b")
        @JsonAnyGetter
        public Map<String, Object> map = new HashMap<>();
    }

    static class IgnoreOnFieldPojo {
        public int a = 1;
        @JsonIgnore
        public int b = 2;
        @JsonAnyGetter
        public Map<String, Object> map = new HashMap<>();
    }

    static class AlphabeticOrderOnAnyGetterBean {
        @JsonPropertyOrder(alphabetic = true)
        @JsonAnyGetter
        public Map<String, Object> map = new LinkedHashMap<>();
    }

    @JsonPropertyOrder(alphabetic = true)
    static class AlphabeticOrderOnClassBean {
        public int c = 3, a = 1, b = 2;
        @JsonAnyGetter
        public Map<String, Object> map = new LinkedHashMap<>();
    }

    static class LinkUnlinkConflictPojo {
        private Map<String, Object> properties = new HashMap<>();

        @JsonAnyGetter
        public Map<String, Object> getProperties() {
            properties.put("key", "value");
            return properties;
        }

        @JsonIgnore
        public String getProperties(String key) {
            return "unrelated";
        }

        @JsonIgnore
        public String getKey() {
            return "unrelated";
        }
    }

    @JsonPropertyOrder({ "firstProperty", "secondProperties", "thirdProperty", "fourthProperty" })
    static class PrivateAnyGetterPojo {
        public int firstProperty = 1, fourthProperty = 4, thirdProperty = 3;

        @JsonAnyGetter
        private Map<String, Object> secondProperties = new HashMap<>();

        public PrivateAnyGetterPojo add(String key, Object value) {
            secondProperties.put(key, value);
            return this;
        }

        public Map<String, Object> secondProperties() {
            return secondProperties;
        }
    }

    @JsonPropertyOrder({ "firstProperty", "secondProperties", "thirdProperty", "fourthProperty" })
    static class PrivateAnyGetterPojoSorted extends PrivateAnyGetterPojo {
        public Map<String, Object> getSecondProperties() {
            return super.secondProperties;
        }
    }

    // [databind#3604]: Allow ObjectNode for @JsonAnyGetter (field)
    static class ObjectNodeAnyGetterFieldBean {
        public int id;

        @JsonAnyGetter
        public ObjectNode extra;
    }

    // [databind#3604]: Allow ObjectNode for @JsonAnyGetter (method)
    static class ObjectNodeAnyGetterMethodBean {
        public int id;
        private ObjectNode extra;

        @JsonAnyGetter
        public ObjectNode getExtra() { return extra; }
        public void setExtra(ObjectNode extra) { this.extra = extra; }
    }

    // [databind#3604]: Allow JsonNode for @JsonAnyGetter (field)
    static class JsonNodeAnyGetterFieldBean {
        public int id;

        @JsonAnyGetter
        public JsonNode extra;
    }

    // For [databind#5215]: Any-getter should be sorted last, by default
    static class DynaBean5215 {
        public String l;
        public String j;
        public String a;

        protected Map<String, Object> extensions = new LinkedHashMap<>();

        @JsonAnyGetter
        public Map<String, Object> getExtensions() {
            return extensions;
        }

        @JsonAnySetter
        public void addExtension(String name, Object value) {
            extensions.put(name, value);
        }
    }

    /*
    /**********************************************************
    /* Test methods
    /**********************************************************
     */

    private final ObjectMapper MAPPER = newJsonMapper();

    // [databind#1458]: Allow `@JsonAnyGetter` on fields too
    @Test
    public void testDynaFieldBean() throws Exception
    {
        DynaFieldBean b = new DynaFieldBean();
        b.id = 123;
        b.set("name", "Billy");
        assertEquals("{\"id\":123,\"name\":\"Billy\"}", MAPPER.writeValueAsString(b));

        DynaFieldBean result = MAPPER.readValue("{\"id\":2,\"name\":\"Joe\"}", DynaFieldBean.class);
        assertEquals(2, result.id);
        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
    public void testSimpleAnyBean() throws Exception
    {
        String json = MAPPER.writeValueAsString(new Bean());
        Map<?,?> map = MAPPER.readValue(json, Map.class);
        assertEquals(2, map.size());
        assertEquals(Integer.valueOf(3), map.get("x"));
        assertEquals(Boolean.TRUE, map.get("a"));
    }

    @Test
    public void testAnyOnly() throws Exception
    {
        ObjectMapper m;

        // First, with normal fail settings:
        m = jsonMapperBuilder()
                .enable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
                .build();
        assertEquals("{\"a\":3}", m.writeValueAsString(new AnyOnlyBean()));

        // then without fail
        String json = m.writer()
                .without(SerializationFeature.FAIL_ON_EMPTY_BEANS)
                .writeValueAsString(new AnyOnlyBean());
        assertEquals("{\"a\":3}", json);
    }

    @Test
    public void testAnyDisabling() throws Exception
    {
        String json = MAPPER.writeValueAsString(new NotEvenAnyBean());
        assertEquals(a2q("{'value':42}"), json);
    }

    // Trying to repro [databind#577]
    @Test
    public void testAnyWithNull() throws Exception
    {
        MapAsAny input = new MapAsAny();
        input.add("bar", null);
        assertEquals(a2q("{'bar':null}"),
                MAPPER.writeValueAsString(input));
    }

    @Test
    public void testIssue705() throws Exception
    {
        Issue705Bean input = new Issue705Bean("key", "value");
        String json = MAPPER.writer()
                .without(JsonWriteFeature.ESCAPE_FORWARD_SLASHES)
                .writeValueAsString(input);
        assertEquals("{\"stuff\":\"[key/value]\"}", json);
    }

    // [databind#1124]
    @Test
    public void testAnyGetterWithValueSerializer() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();
        Bean1124 input = new Bean1124();
        input.addAdditionalProperty("key", "value");
        String json = mapper.writeValueAsString(input);
        assertEquals("{\"key\":\"VALUE\"}", json);
    }

    // [databind#2592]
    @Test
    public void testAnyGetterWithMapperDefaultIncludeNonEmpty() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .changeDefaultPropertyInclusion(incl -> incl
                        .withValueInclusion(JsonInclude.Include.NON_EMPTY)
                        .withContentInclusion(JsonInclude.Include.NON_EMPTY))
                .build();
        Bean2592NoAnnotations input = new Bean2592NoAnnotations();
        input.add("non-empty", "property");
        input.add("empty", "");
        input.add("null", null);
        String json = mapper.writeValueAsString(input);
        assertEquals("{\"non-empty\":\"property\"}", json);
    }

    // [databind#2592]
    @Test
    public void testAnyGetterWithMapperDefaultIncludeNonEmptyAndFilterOnBean() throws Exception
    {
        FilterProvider filters = new SimpleFilterProvider()
                .addFilter("Bean2592", SimpleBeanPropertyFilter.serializeAllExcept("something"));
        ObjectMapper mapper = jsonMapperBuilder()
                .changeDefaultPropertyInclusion(incl -> incl
                        .withValueInclusion(JsonInclude.Include.NON_EMPTY)
                        .withContentInclusion(JsonInclude.Include.NON_EMPTY))
                .filterProvider(filters)
                .build();
        Bean2592WithFilter input = new Bean2592WithFilter();
        input.add("non-empty", "property");
        input.add("empty", "");
        input.add("null", null);
        String json = mapper.writeValueAsString(input);
        assertEquals("{\"non-empty\":\"property\"}", json);
    }

    // [databind#2592]
    @Test
    public void testAnyGetterWithPropertyIncludeNonEmpty() throws Exception
    {
        ObjectMapper mapper = new ObjectMapper();
        Bean2592PropertyIncludeNonEmpty input = new Bean2592PropertyIncludeNonEmpty();
        input.add("non-empty", "property");
        input.add("empty", "");
        input.add("null", null);
        String json = mapper.writeValueAsString(input);
        assertEquals("{\"non-empty\":\"property\"}", json);
    }

    // [databind#2592]
    @Test
    public void testAnyGetterConfigIncludeNonEmpty() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .withConfigOverride(Map.class, incl -> incl.setInclude(
                    JsonInclude.Value.construct(JsonInclude.Include.USE_DEFAULTS,
                    JsonInclude.Include.NON_EMPTY)))
                .build();
        Bean2592NoAnnotations input = new Bean2592NoAnnotations();
        input.add("non-empty", "property");
        input.add("empty", "");
        input.add("null", null);
        String json = mapper.writeValueAsString(input);
        assertEquals("{\"non-empty\":\"property\"}", json);
    }

    // For [databind#518]
    @Test
    void anyBeanWithSort518() throws Exception
    {
        Map<String,Object> extra = new LinkedHashMap<>();
        extra.put("y", 4);
        extra.put("x", 3);
        String json = MAPPER.writeValueAsString(new Bean518(2, 1, extra));
        assertEquals(a2q("{'a':2,'b':1,'y':4,'x':3}"), json);
    }

    // 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 {
        // Respect @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," +
                "'fourthProperty':4," +
                "'secondProperty':2}"),
            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," +
                "'thirdProperty':3," +
                "'fourthProperty':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);
    }

    // [databind#3604]: Allow ObjectNode field for @JsonAnyGetter
    @Test
    public void testAnyGetterWithObjectNodeField() throws Exception
    {
        ObjectNodeAnyGetterFieldBean bean = new ObjectNodeAnyGetterFieldBean();
        bean.id = 1;
        bean.extra = MAPPER.createObjectNode();
        bean.extra.put("a", 2);
        bean.extra.put("b", "text");

        String json = MAPPER.writeValueAsString(bean);
        assertEquals(a2q("{'id':1,'a':2,'b':'text'}"), json);
    }

    // [databind#3604]: Allow ObjectNode method for @JsonAnyGetter
    @Test
    public void testAnyGetterWithObjectNodeMethod() throws Exception
    {
        ObjectNodeAnyGetterMethodBean bean = new ObjectNodeAnyGetterMethodBean();
        bean.id = 1;
        ObjectNode node = MAPPER.createObjectNode();
        node.put("x", true);
        node.put("y", 42);
        bean.setExtra(node);

        String json = MAPPER.writeValueAsString(bean);
        assertEquals(a2q("{'id':1,'x':true,'y':42}"), json);
    }

    // [databind#3604]: Null ObjectNode for @JsonAnyGetter should be fine
    @Test
    public void testAnyGetterWithNullObjectNode() throws Exception
    {
        ObjectNodeAnyGetterFieldBean bean = new ObjectNodeAnyGetterFieldBean();
        bean.id = 1;
        bean.extra = null;

        String json = MAPPER.writeValueAsString(bean);
        assertEquals(a2q("{'id':1}"), json);
    }

    // [databind#3604]: JsonNode (ObjectNode) field for @JsonAnyGetter
    @Test
    public void testAnyGetterWithJsonNodeField() throws Exception
    {
        JsonNodeAnyGetterFieldBean bean = new JsonNodeAnyGetterFieldBean();
        bean.id = 1;
        ObjectNode node = MAPPER.createObjectNode();
        node.put("name", "test");
        bean.extra = node;

        String json = MAPPER.writeValueAsString(bean);
        assertEquals(a2q("{'id':1,'name':'test'}"), json);
    }

    // [databind#3604]: Empty ObjectNode for @JsonAnyGetter should produce no extra properties
    @Test
    public void testAnyGetterWithEmptyObjectNode() throws Exception
    {
        ObjectNodeAnyGetterFieldBean bean = new ObjectNodeAnyGetterFieldBean();
        bean.id = 1;
        bean.extra = MAPPER.createObjectNode();

        String json = MAPPER.writeValueAsString(bean);
        assertEquals(a2q("{'id':1}"), json);
    }

    // [databind#3604]: Non-ObjectNode JsonNode (e.g. ArrayNode) should fail with clear error
    @Test
    public void testAnyGetterWithArrayNodeFails() throws Exception
    {
        JsonNodeAnyGetterFieldBean bean = new JsonNodeAnyGetterFieldBean();
        bean.id = 1;
        bean.extra = MAPPER.createArrayNode().add(1).add(2);

        DatabindException ex = assertThrows(DatabindException.class,
                () -> MAPPER.writeValueAsString(bean));
        assertThat(ex.getMessage()).contains("ObjectNode");
    }

    // For [databind#5215]: Any-getter should be sorted last, by default
    @Test
    public void dynaBean5215() throws Exception
    {
        final ObjectMapper mapper = JsonMapper.builder()
                .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
                .configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
                .build();

        DynaBean5215 b = new DynaBean5215();
        b.a = "1";
        b.j = "2";
        b.l = "3";
        b.addExtension("z", "5");
        b.addExtension("b", "4");
        assertEquals(a2q("{" +
                "'a':'1'," +
                "'j':'2'," +
                "'l':'3'," +
                "'b':'4'," +
                "'z':'5'}"), mapper.writeValueAsString(b));
    }
}