diff --git a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java index 572f6392a0..4e5e88b971 100644 --- a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java @@ -375,6 +375,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 e17f7d3bf7..5c5d0c6e0f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java @@ -188,6 +188,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 695ff38129..ee01876f80 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java @@ -1115,7 +1115,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 1434eea3b0..0044db1f8b 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java @@ -126,7 +126,7 @@ public void testCopy() throws Exception assertTrue(m.isEnabled(JsonParser.Feature.IGNORE_UNDEFINED)); // // First: verify that handling of features is decoupled: - + ObjectMapper m2 = m.copy(); assertFalse(m2.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); m2.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); @@ -215,7 +215,7 @@ public void testFailedCopy() throws Exception } } - public void testAnnotationIntrospectorCopyin() + public void testAnnotationIntrospectorCopying() { ObjectMapper m = new ObjectMapper(); m.setAnnotationIntrospector(new MyAnnotationIntrospector()); @@ -272,6 +272,32 @@ public void testConfigForPropertySorting() throws Exception assertTrue(dc.shouldSortPropertiesAlphabetically()); } + // 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.getSerializationConfig(); + assertFalse(sc.isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING)); + assertFalse(sc.shouldPreservePropertiesOrdering()); + DeserializationConfig dc = m.getDeserializationConfig(); + assertFalse(dc.shouldPreservePropertiesOrdering()); + + // but when enabled, should be visible: + m = jsonMapperBuilder() + .enable(MapperFeature.STRICT_PROPERTIES_ORDERING) + .build(); + sc = m.getSerializationConfig(); + assertTrue(sc.isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING)); + assertTrue(sc.shouldPreservePropertiesOrdering()); + dc = m.getDeserializationConfig(); + // and not just via SerializationConfig, but also via DeserializationConfig + assertTrue(dc.isEnabled(MapperFeature.STRICT_PROPERTIES_ORDERING)); + assertTrue(dc.shouldPreservePropertiesOrdering()); + } + public void testJsonFactoryLinkage() { // first, implicit factory, giving implicit linkage @@ -284,7 +310,7 @@ public void testJsonFactoryLinkage() assertSame(m, f.getCodec()); } - public void testProviderConfig() throws Exception + public void testProviderConfig() throws Exception { ObjectMapper m = new ObjectMapper(); final String JSON = "{ \"x\" : 3 }"; @@ -332,7 +358,7 @@ public void testCustomDefaultPrettyPrinter() throws Exception assertEquals("[1,2]", m.writer().without(SerializationFeature.INDENT_OUTPUT) .writeValueAsString(input)); } - + // For [databind#703], [databind#978] public void testNonSerializabilityOfObject() { 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 67807c56f7..045b70e7aa 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; } + } + // For [databind#2879] @JsonPropertyOrder({ "a", "c" }) static class BeanFor2879 { @@ -126,6 +142,11 @@ static class OrderingByIndexBean { .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) .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))); @@ -187,4 +208,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); + } }