diff --git a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java index 97da09ab3b..e073e84080 100644 --- a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java @@ -271,6 +271,20 @@ public enum MapperFeature implements ConfigFeature */ SORT_PROPERTIES_ALPHABETICALLY(false), + /** + * Feature that enforces strict ordering as requested by other configuration methods + * for POJO fields (note: does not apply to {@link java.util.Map} + * serialization!): + * if enabled, ordering is preserved even if {@link com.fasterxml.jackson.annotation.JsonCreator} + * is present. Without this feature properties referenced by {@link com.fasterxml.jackson.annotation.JsonCreator} + * taking precedence over other properties even if sorting is requested. + *

+ * Note that if ordering is not enabled using other ways, this feature has no effect. + *

+ * Feature is disabled by default. + */ + STRICT_PROPERTIES_ORDERING(false), + /* /********************************************************************** /* Name-related features diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java index 735e6c4f9f..710d3a3683 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java @@ -133,6 +133,14 @@ public final boolean shouldSortPropertiesAlphabetically() { return isEnabled(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); } + /** + * Accessor for checking whether default settings for forcing property + * ordering is enabled. + */ + public final boolean shouldPreservePropertiesOrdering() { + return isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING); + } + /** * Accessor for checking whether configuration indicates that * "root wrapping" (use of an extra property/name pair at root level) 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 7835a53912..3f1d297e15 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java @@ -1034,7 +1034,8 @@ protected void _sortProperties(Map props) } // Third by sorting Creator properties before other unordered properties - if (_creatorProperties != null) { + // (unless strict ordering is requested) + if (_creatorProperties != null && !_config.shouldPreservePropertiesOrdering()) { /* As per [databind#311], this is bit delicate; but if alphabetic ordering * is mandated, at least ensure creator properties are in alphabetic * order. Related question of creator vs non-creator is punted for now, diff --git a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java index 9871781f80..33af2fe0f0 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java @@ -143,7 +143,33 @@ public void testConfigForPropertySorting() throws Exception assertTrue(dc.shouldSortPropertiesAlphabetically()); } - public void testDeserializationContextCache() throws Exception + // Test to ensure that we can check forced property ordering defaults... + public void testConfigForForcedPropertySorting() throws Exception + { + ObjectMapper m = new ObjectMapper(); + + // sort-alphabetically is disabled by default: + assertFalse(m.isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING)); + SerializationConfig sc = m.serializationConfig(); + assertFalse(sc.isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING)); + assertFalse(sc.shouldPreservePropertiesOrdering()); + DeserializationConfig dc = m.deserializationConfig(); + assertFalse(dc.shouldPreservePropertiesOrdering()); + + // but when enabled, should be visible: + m = jsonMapperBuilder() + .enable(MapperFeature.STRICT_PROPERTIES_ORDERING) + .build(); + sc = m.serializationConfig(); + assertTrue(sc.isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING)); + assertTrue(sc.shouldPreservePropertiesOrdering()); + dc = m.deserializationConfig(); + // and not just via SerializationConfig, but also via DeserializationConfig + assertTrue(dc.isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING)); + assertTrue(dc.shouldPreservePropertiesOrdering()); + } + + public void testDeserializationContextCache() throws Exception { ObjectMapper m = new ObjectMapper(); final String JSON = "{ \"x\" : 3 }"; diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/SerializationOrderTest.java b/src/test/java/com/fasterxml/jackson/databind/ser/SerializationOrderTest.java index d27195b40c..2399af667d 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/SerializationOrderTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/SerializationOrderTest.java @@ -82,6 +82,22 @@ public BeanForGH311(@JsonProperty("b") int b, @JsonProperty("a") int a) { //b an public int getB() { return b; } } + static class BeanForStrictOrdering { + private final int a; + private int b; + private final int c; + + @JsonCreator + public BeanForStrictOrdering(@JsonProperty("c") int c, @JsonProperty("a") int a) { //b and a are out of order, although alphabetic = true + this.a = a; + this.c = c; + } + + public int getA() { return a; } + public int getB() { return b; } + public int getC() { return c; } + } + // We'll expect ordering of "FUBAR" @JsonPropertyOrder({ "f" }) static class OrderingByIndexBean { @@ -109,6 +125,11 @@ static class OrderingByIndexBean { .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) .build(); + private final ObjectMapper STRICT_ALPHA_MAPPER = jsonMapperBuilder() + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .enable(MapperFeature.STRICT_PROPERTIES_ORDERING) + .build(); + public void testImplicitOrderByCreator() throws Exception { assertEquals("{\"c\":1,\"a\":2,\"b\":0}", MAPPER.writeValueAsString(new BeanWithCreator(1, 2))); @@ -160,4 +181,10 @@ public void testOrderByIndexEtc() throws Exception assertEquals(aposToQuotes("{'f':0,'u':0,'b':0,'a':0,'r':0}"), ALPHA_MAPPER.writeValueAsString(new OrderingByIndexBean())); } + + public void testStrictAlphaAndCreatorOrdering() throws Exception + { + String json = STRICT_ALPHA_MAPPER.writeValueAsString(new BeanForStrictOrdering(1, 2)); + assertEquals("{\"a\":2,\"b\":0,\"c\":1}", json); + } }