AnyGetterOrdering4388Test.java

package com.fasterxml.jackson.databind.ser;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

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 static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * Test to verify that the order of properties is preserved when using @JsonPropertyOrder
 * with @JsonUnwrapped and @JsonAnyGetter
 */
public class AnyGetterOrdering4388Test extends DatabindTestUtil {
    // Base class with properties
    static class BaseWithProperties {
        public String entityName;
        public int entityId;
        public Integer totalTests;
        @JsonAnyGetter
        public Map<String, Object> 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<String, Object> map = new HashMap<>();
    }

    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) {
            // 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<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", "forthProperty" })
    static class PrivateAnyGetterPojoSorted extends PrivateAnyGetterPojo {
        public Map<String, Object> 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);
    }
}