From 563470e6ea72eead488e917ed939b4a3d5abded5 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 7 Nov 2015 21:17:37 -0800 Subject: [PATCH 001/124] Fixed #857 --- release-notes/CREDITS | 5 ++ release-notes/VERSION | 2 + .../JacksonAnnotationIntrospector.java | 10 ++- .../TestPOJOPropertiesCollector.java | 4 +- .../introspect/TransientFieldTest.java | 32 ---------- .../databind/introspect/TransientTest.java | 61 +++++++++++++++++++ 6 files changed, 79 insertions(+), 35 deletions(-) delete mode 100644 src/test/java/com/fasterxml/jackson/databind/introspect/TransientFieldTest.java create mode 100644 src/test/java/com/fasterxml/jackson/databind/introspect/TransientTest.java diff --git a/release-notes/CREDITS b/release-notes/CREDITS index b3f913ae93..693d23a33c 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -362,3 +362,8 @@ Miles Kaufmann (milesk-amzn@github) Jirka Kremser (Jiri-Kremser@github) * Suggested #924: SequenceWriter.writeAll() could accept Iterable (2.7.0) + +Thomas Mortagne (tmortagne@github) + * Suggested #857: Add support for java.beans.Transient + (2.7.0) + diff --git a/release-notes/VERSION b/release-notes/VERSION index 07155e5473..e122dcf3bf 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -14,6 +14,8 @@ Project: jackson-databind (reported by Miles K) #497: Add new JsonInclude.Include feature to exclude maps after exclusion removes all elements #819: Add support for setting `FormatFeature` via `ObjectReader`, `ObjectWriter` +#857: Add support for java.beans.Transient + (suggested by Thomas M) #898: Add `ObjectMapper.getSerializerProviderInstance()` #915: ObjectMapper default timezone is GMT, should be UTC (suggested by Infrag@github) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index 3fda325e06..9c9d807a8c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -1,5 +1,6 @@ package com.fasterxml.jackson.databind.introspect; +import java.beans.Transient; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.*; @@ -1004,7 +1005,14 @@ public JsonCreator.Mode findCreatorBinding(Annotated a) { protected boolean _isIgnorable(Annotated a) { JsonIgnore ann = _findAnnotation(a, JsonIgnore.class); - return (ann != null && ann.value()); + if (ann != null) { + return ann.value(); + } + Transient t = _findAnnotation(a, Transient.class); + if (t != null) { + return t.value(); + } + return false; } protected Class _classIfExplicit(Class cls) { diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java index cf961c9e52..eafc0d0a37 100644 --- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java @@ -237,8 +237,8 @@ public DuplicateGetterCreatorBean(@JsonProperty("bloop") @A boolean bloop) {} /********************************************************** */ - private final ObjectMapper MAPPER = new ObjectMapper(); - + private final ObjectMapper MAPPER = objectMapper(); + public void testSimple() { POJOPropertiesCollector coll = collector(MAPPER, diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TransientFieldTest.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TransientFieldTest.java deleted file mode 100644 index 7c58ce0476..0000000000 --- a/src/test/java/com/fasterxml/jackson/databind/introspect/TransientFieldTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.fasterxml.jackson.databind.introspect; - -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.databind.*; - -// tests for [databind#296] -public class TransientFieldTest extends BaseMapTest -{ - @JsonPropertyOrder({ "x" }) - static class ClassyTransient - { - public transient int value = 3; - - public int getValue() { return value; } - - public int getX() { return 42; } - } - - public void testTransientFieldHandling() throws Exception - { - // default handling: remove transient field but do not propagate - ObjectMapper m = objectMapper(); - assertEquals(aposToQuotes("{'x':42,'value':3}"), - m.writeValueAsString(new ClassyTransient())); - - // but may change that - m = new ObjectMapper() - .enable(MapperFeature.PROPAGATE_TRANSIENT_MARKER); - assertEquals(aposToQuotes("{'x':42}"), - m.writeValueAsString(new ClassyTransient())); - } -} diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TransientTest.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TransientTest.java new file mode 100644 index 0000000000..484c753c91 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TransientTest.java @@ -0,0 +1,61 @@ +package com.fasterxml.jackson.databind.introspect; + +import java.beans.Transient; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.*; + +/** + * Tests for both `transient` keyword and JDK 7 + * {@link java.beans.Transient} annotation. + */ +public class TransientTest extends BaseMapTest +{ + // for [databind#296] + @JsonPropertyOrder({ "x" }) + static class ClassyTransient + { + public transient int value = 3; + + public int getValue() { return value; } + + public int getX() { return 42; } + } + + // for [databind#857] + static class BeanTransient { + @Transient + public int getX() { return 3; } + + public int getY() { return 4; } + } + + /* + /********************************************************** + /* Unit tests + /********************************************************** + */ + + private final ObjectMapper MAPPER = objectMapper(); + + // for [databind#296] + public void testTransientFieldHandling() throws Exception + { + // default handling: remove transient field but do not propagate + assertEquals(aposToQuotes("{'x':42,'value':3}"), + MAPPER.writeValueAsString(new ClassyTransient())); + + // but may change that + ObjectMapper m = new ObjectMapper() + .enable(MapperFeature.PROPAGATE_TRANSIENT_MARKER); + assertEquals(aposToQuotes("{'x':42}"), + m.writeValueAsString(new ClassyTransient())); + } + + // for [databind#857] + public void testBeanTransient() throws Exception + { + assertEquals(aposToQuotes("{'y':4}"), + MAPPER.writeValueAsString(new BeanTransient())); + } +} From f89737c46e7adaef5c18874dc68b78adbdd9f2d9 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 7 Nov 2015 21:34:38 -0800 Subject: [PATCH 002/124] Fix #978 --- release-notes/CREDITS | 4 ++++ release-notes/VERSION | 2 ++ .../databind/ser/DefaultSerializerProvider.java | 9 +++++++++ .../jackson/databind/ObjectMapperTest.java | 13 +++++++++++-- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 693d23a33c..8655d414bf 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -367,3 +367,7 @@ Thomas Mortagne (tmortagne@github) * Suggested #857: Add support for java.beans.Transient (2.7.0) +Shumpei Akai (flexfrank@github) + * Reported #978: ObjectMapper#canSerialize(Object.class) returns false even though + FAIL_ON_EMPTY_BEANS is disabled + (2.7.0) diff --git a/release-notes/VERSION b/release-notes/VERSION index e122dcf3bf..5094c41db4 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -31,6 +31,8 @@ Project: jackson-databind (contributed by Jesse W) #957: Merge `datatype-jdk7` stuff in (java.nio.file.Path handling) #959: Schema generation: consider active view, discard non-included properties +#978: ObjectMapper#canSerialize(Object.class) returns false even though FAIL_ON_EMPTY_BEANS is disabled + (reported by Shumpei A) #997: Add `MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS` #998: Allow use of `NON_DEFAULT` for POJOs without default constructor - Make `JsonValueFormat` (self-)serializable, deserializable, to/from valid external diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java index 9e85e30bfd..b4984f3727 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/DefaultSerializerProvider.java @@ -418,6 +418,15 @@ public void acceptJsonFormatVisitor(JavaType javaType, JsonFormatVisitorWrapper */ public boolean hasSerializerFor(Class cls, AtomicReference cause) { + // 07-Nov-2015, tatu: One special case, Object.class; will work only if + // empty beans are allowed or custom serializer registered. Easiest to + // check here. + if (cls == Object.class) { + if (!_config.isEnabled(SerializationFeature.FAIL_ON_EMPTY_BEANS)) { + return true; + } + } + try { JsonSerializer ser = _findExplicitUntypedSerializer(cls); return (ser != null); diff --git a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java index 37727eca3d..0db899dfe8 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/ObjectMapperTest.java @@ -242,13 +242,22 @@ public void testCustomDefaultPrettyPrinter() throws Exception .writeValueAsString(input)); } - // For [databind#703] + // For [databind#703], [databind#978] public void testNonSerializabilityOfObject() { ObjectMapper m = new ObjectMapper(); assertFalse(m.canSerialize(Object.class)); - // but this used to pass, incorrectly + // but this used to pass, incorrectly, second time around assertFalse(m.canSerialize(Object.class)); + + // [databind#978]: Different answer if empty Beans ARE allowed + m = new ObjectMapper(); + m.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + assertTrue(m.canSerialize(Object.class)); + assertTrue(MAPPER.writer().without(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .canSerialize(Object.class)); + assertFalse(MAPPER.writer().with(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .canSerialize(Object.class)); } // for [databind#756] From 51a02d02e8c801959ba6701b63e03170b055acfe Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 8 Nov 2015 15:07:35 -0800 Subject: [PATCH 003/124] Fixed #905 --- release-notes/CREDITS | 4 + release-notes/VERSION | 2 + .../JacksonAnnotationIntrospector.java | 107 ++++++++++++------ .../databind/creators/TestCreators2.java | 23 +++- 4 files changed, 97 insertions(+), 39 deletions(-) diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 8655d414bf..b564047364 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -371,3 +371,7 @@ Shumpei Akai (flexfrank@github) * Reported #978: ObjectMapper#canSerialize(Object.class) returns false even though FAIL_ON_EMPTY_BEANS is disabled (2.7.0) + +Jonas Konrad (yawkat@github) + * Suggested #905: Add support for `@ConstructorProperties` + (2.7.0) diff --git a/release-notes/VERSION b/release-notes/VERSION index 5094c41db4..b5193006a5 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -17,6 +17,8 @@ Project: jackson-databind #857: Add support for java.beans.Transient (suggested by Thomas M) #898: Add `ObjectMapper.getSerializerProviderInstance()` +#905: Add support for `@ConstructorProperties` + (requested by Jonas K) #915: ObjectMapper default timezone is GMT, should be UTC (suggested by Infrag@github) #918: Add `MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING` diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index 9c9d807a8c..11bbf82f7a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -1,5 +1,6 @@ package com.fasterxml.jackson.databind.introspect; +import java.beans.ConstructorProperties; import java.beans.Transient; import java.lang.annotation.Annotation; import java.lang.reflect.Field; @@ -779,24 +780,24 @@ protected BeanPropertyWriter _constructVirtualProperty(JsonAppend.Prop prop, @Override public PropertyName findNameForSerialization(Annotated a) { - String name = null; - JsonGetter jg = _findAnnotation(a, JsonGetter.class); if (jg != null) { - name = jg.value(); - } else { - JsonProperty pann = _findAnnotation(a, JsonProperty.class); - if (pann != null) { - name = pann.value(); - } else if (_hasAnnotation(a, JsonSerialize.class) - || _hasAnnotation(a, JsonView.class) - || _hasAnnotation(a, JsonRawValue.class)) { - name = ""; - } else { - return null; - } + return PropertyName.construct(jg.value()); + } + JsonProperty pann = _findAnnotation(a, JsonProperty.class); + if (pann != null) { + return PropertyName.construct(pann.value()); } - return PropertyName.construct(name); + PropertyName ctorName = _findConstructorName(a); + if (ctorName != null) { + return ctorName; + } + if (_hasAnnotation(a, JsonSerialize.class) + || _hasAnnotation(a, JsonView.class) + || _hasAnnotation(a, JsonRawValue.class)) { + return PropertyName.USE_DEFAULT; + } + return null; } @Override @@ -932,32 +933,33 @@ public JsonPOJOBuilder.Value findPOJOBuilderConfig(AnnotatedClass ac) @Override public PropertyName findNameForDeserialization(Annotated a) { - String name; - // @JsonSetter has precedence over @JsonProperty, being more specific // @JsonDeserialize implies that there is a property, but no name JsonSetter js = _findAnnotation(a, JsonSetter.class); if (js != null) { - name = js.value(); - } else { - JsonProperty pann = _findAnnotation(a, JsonProperty.class); - if (pann != null) { - name = pann.value(); - /* 22-Apr-2014, tatu: Should figure out a better way to do this, but - * it's actually bit tricky to do it more efficiently (meta-annotations - * add more lookups; AnnotationMap costs etc) - */ - } else if (_hasAnnotation(a, JsonDeserialize.class) - || _hasAnnotation(a, JsonView.class) - || _hasAnnotation(a, JsonUnwrapped.class) // [#442] - || _hasAnnotation(a, JsonBackReference.class) - || _hasAnnotation(a, JsonManagedReference.class)) { - name = ""; - } else { - return null; - } + return PropertyName.construct(js.value()); } - return PropertyName.construct(name); + JsonProperty pann = _findAnnotation(a, JsonProperty.class); + if (pann != null) { + return PropertyName.construct(pann.value()); + } + PropertyName ctorName = _findConstructorName(a); + if (ctorName != null) { + return ctorName; + } + + /* 22-Apr-2014, tatu: Should figure out a better way to do this, but + * it's actually bit tricky to do it more efficiently (meta-annotations + * add more lookups; AnnotationMap costs etc) + */ + if (_hasAnnotation(a, JsonDeserialize.class) + || _hasAnnotation(a, JsonView.class) + || _hasAnnotation(a, JsonUnwrapped.class) // [databind#442] + || _hasAnnotation(a, JsonBackReference.class) + || _hasAnnotation(a, JsonManagedReference.class)) { + return PropertyName.USE_DEFAULT; + } + return null; } @Override @@ -987,7 +989,18 @@ public boolean hasCreatorAnnotation(Annotated a) * to this method getting called) */ JsonCreator ann = _findAnnotation(a, JsonCreator.class); - return (ann != null && ann.mode() != JsonCreator.Mode.DISABLED); + if (ann != null) { + return (ann.mode() != JsonCreator.Mode.DISABLED); + } + if (a instanceof AnnotatedConstructor) { + ConstructorProperties props = _findAnnotation(a, ConstructorProperties.class); + // 08-Nov-2015, tatu: One possible check would be to ensure there is at least + // one name iff constructor has arguments. But seems unnecessary for now. + if (props != null) { + return true; + } + } + return false; } @Override @@ -1037,6 +1050,26 @@ protected PropertyName _propertyName(String localName, String namespace) { return PropertyName.construct(localName, namespace); } + protected PropertyName _findConstructorName(Annotated a) + { + if (a instanceof AnnotatedParameter) { + AnnotatedParameter p = (AnnotatedParameter) a; + AnnotatedWithParams ctor = p.getOwner(); + + if (ctor != null) { + ConstructorProperties props = _findAnnotation(ctor, ConstructorProperties.class); + if (props != null) { + String[] names = props.value(); + int ix = p.getIndex(); + if (ix < names.length) { + return PropertyName.construct(names[ix]); + } + } + } + } + return null; + } + /** * Helper method called to construct and initialize instance of {@link TypeResolverBuilder} * if given annotated element indicates one is needed. diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators2.java b/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators2.java index fb41a17363..d75ec0b160 100644 --- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators2.java +++ b/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators2.java @@ -1,12 +1,12 @@ package com.fasterxml.jackson.databind.creators; +import java.beans.ConstructorProperties; import java.io.IOException; import java.util.List; import java.util.Map; import com.fasterxml.jackson.annotation.*; - import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -161,7 +161,17 @@ public Issue700Bean(@JsonProperty("item") String item) { } public String getItem() { return null; } } - + + static class Issue905Bean { + public int v1, v2; + + @ConstructorProperties({"x", "y"}) + public Issue905Bean(int a, int b) { + v1 = a; + v2 = b; + } + } + /* /********************************************************** /* Test methods @@ -297,4 +307,13 @@ public void testCreatorProperties() throws Exception Issue700Bean value = MAPPER.readValue("{ \"item\" : \"foo\" }", Issue700Bean.class); assertNotNull(value); } + + // [databind#905] + public void testCreatorPropertiesAnnotation() throws Exception + { + Issue905Bean b = MAPPER.readValue(aposToQuotes("{'y':3,'x':2}"), + Issue905Bean.class); + assertEquals(2, b.v1); + assertEquals(3, b.v2); + } } From 321562a3e7bb35a8233f3e9536580dd5858d7d36 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 8 Nov 2015 22:06:33 -0800 Subject: [PATCH 004/124] Add javadocs to indicate why alternate naming used in tests. --- .../jackson/databind/creators/TestCreators2.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators2.java b/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators2.java index d75ec0b160..3c1d5aab4b 100644 --- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators2.java +++ b/src/test/java/com/fasterxml/jackson/databind/creators/TestCreators2.java @@ -163,12 +163,18 @@ public Issue700Bean(@JsonProperty("item") String item) { } } static class Issue905Bean { - public int v1, v2; + // 08-Nov-2015, tatu: Note that in real code we would most likely use same + // names for properties; but here we use different name on purpose to + // ensure that Jackson has no way of binding JSON properties "x" and "y" + // using any other mechanism than via `@ConstructorProperties` annotation + public int _x, _y; @ConstructorProperties({"x", "y"}) + // Same as above; use differing local parameter names so that parameter name + // introspection can not be used as the source of property names. public Issue905Bean(int a, int b) { - v1 = a; - v2 = b; + _x = a; + _y = b; } } @@ -313,7 +319,7 @@ public void testCreatorPropertiesAnnotation() throws Exception { Issue905Bean b = MAPPER.readValue(aposToQuotes("{'y':3,'x':2}"), Issue905Bean.class); - assertEquals(2, b.v1); - assertEquals(3, b.v2); + assertEquals(2, b._x); + assertEquals(3, b._y); } } From 6aaa38baa757e1e58dca85df21be99a6db83e6a6 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 9 Nov 2015 19:09:36 -0800 Subject: [PATCH 005/124] Fix a minor problem with `@JsonNaming` not recognizing default value ("no naming") --- release-notes/VERSION | 1 + .../databind/annotation/JsonNaming.java | 3 +++ .../introspect/POJOPropertiesCollector.java | 5 +++++ .../introspect/TestNamingStrategyStd.java | 22 +++++++++++++++++++ 4 files changed, 31 insertions(+) diff --git a/release-notes/VERSION b/release-notes/VERSION index fe08c1325e..ddda92c615 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -10,6 +10,7 @@ Project: jackson-databind (reported by Antibrumm@github) #989: Deserialization from "{}" to java.lang.Object causes "out of END_OBJECT token" error (reported by Ievgen P) +- Fix a minor problem with `@JsonNaming` not recognizing default value 2.6.3 (12-Oct-2015) diff --git a/src/main/java/com/fasterxml/jackson/databind/annotation/JsonNaming.java b/src/main/java/com/fasterxml/jackson/databind/annotation/JsonNaming.java index 7b94610bb5..507cd3401b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/annotation/JsonNaming.java +++ b/src/main/java/com/fasterxml/jackson/databind/annotation/JsonNaming.java @@ -7,6 +7,9 @@ /** * Annotation that can be used to indicate a {@link PropertyNamingStrategy} * to use for annotated class. Overrides the global (default) strategy. + * Note that if the {@link #value} property is omitted, its default value + * means "use default naming" (that is, no alternate naming method is used). + * This can be used as an override with mix-ins. * * @since 2.1 */ 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 1fb5780a25..b3f32167ca 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java @@ -1007,6 +1007,11 @@ private PropertyNamingStrategy _findNamingStrategy() +namingDef.getClass().getName()+"; expected type PropertyNamingStrategy or Class instead"); } Class namingClass = (Class)namingDef; + // 09-Nov-2015, tatu: Need to consider pseudo-value of STD, which means "use default" + if (namingClass == PropertyNamingStrategy.class) { + return null; + } + if (!PropertyNamingStrategy.class.isAssignableFrom(namingClass)) { throw new IllegalStateException("AnnotationIntrospector returned Class " +namingClass.getName()+"; expected Class"); diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java index ccd76c0700..a332e027b5 100644 --- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java @@ -96,6 +96,20 @@ public static class ClassWithObjectNodeField { public ObjectNode json; } + static class ExplicitBean { + @JsonProperty("firstName") + String userFirstName = "Peter"; + @JsonProperty("lastName") + String userLastName = "Venkman"; + @JsonProperty + String userAge = "35"; + } + + @JsonNaming() + static class DefaultNaming { + public int someValue = 3; + } + /* /********************************************************** /* Set up @@ -316,4 +330,12 @@ public void testNamingWithObjectNode() throws Exception assertEquals(2, result.json.size()); assertEquals("bing", result.json.path("baz").asText()); } + + // Also verify that "no naming strategy" should be ok + public void testExplicitNoNaming() throws Exception + { + ObjectMapper mapper = objectMapper(); + String json = mapper.writeValueAsString(new DefaultNaming()); + assertEquals(aposToQuotes("{'someValue':3}"), json); + } } From cebfddbae8e9fb585cc03f4f4706e268b767b5a9 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 9 Nov 2015 19:41:02 -0800 Subject: [PATCH 006/124] Fixed #909 --- release-notes/VERSION | 3 + .../databind/PropertyNamingStrategy.java | 71 ++++++++++++++++--- .../JacksonAnnotationIntrospector.java | 4 +- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/release-notes/VERSION b/release-notes/VERSION index bf33af8a2a..8cb06c9413 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -19,6 +19,9 @@ Project: jackson-databind #898: Add `ObjectMapper.getSerializerProviderInstance()` #905: Add support for `@ConstructorProperties` (requested by Jonas K) +#909: Rename PropertyNamingStrategy CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES as SNAKE_CASE, + PASCAL_CASE_TO_CAMEL_CASE as UPPER_CAMEL_CASE + (suggested by marcottedan@github) #915: ObjectMapper default timezone is GMT, should be UTC (suggested by Infrag@github) #918: Add `MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING` diff --git a/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java b/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java index d3e3a32469..cfadb42682 100644 --- a/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java +++ b/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java @@ -27,24 +27,40 @@ * characters). */ @SuppressWarnings("serial") -public abstract class PropertyNamingStrategy +public class PropertyNamingStrategy // NOTE: was abstract until 2.7 implements java.io.Serializable { /** - * See {@link LowerCaseWithUnderscoresStrategy} for details. + * Naming convention used in languages like C, where words are in lower-case + * letters, separated by underscores. + * See {@link SnakeCaseStrategy} for details. + * + * @since 2.7 (was formerly called {@link #CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES}) */ - public static final PropertyNamingStrategy CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES = - new LowerCaseWithUnderscoresStrategy(); + public static final PropertyNamingStrategy SNAKE_CASE = new SnakeCaseStrategy(); /** + * Naming convention used in languages like Pascal, where words are capitalized + * and no separator is used between words. * See {@link PascalCaseStrategy} for details. - * - * @since 2.1 + * + * @since 2.7 (was formerly called {@link #PASCAL_CASE_TO_CAMEL_CASE}) */ - public static final PropertyNamingStrategy PASCAL_CASE_TO_CAMEL_CASE = - new PascalCaseStrategy(); + public static final PropertyNamingStrategy UPPER_CAMEL_CASE = new UpperCamelCaseStrategy(); /** + * Naming convention used in Java, where words other than first are capitalized + * and no separator is used between words. Since this is the native Java naming convention, + * naming strategy will not do any transformation between names in data (JSON) and + * POJOS. + * + * @since 2.7 (was formerly called {@link #PASCAL_CASE_TO_CAMEL_CASE}) + */ + public static final PropertyNamingStrategy LOWER_CAMEL_CASE = new PropertyNamingStrategy(); + + /** + * Naming convention in which all words of the logical name are in lower case, and + * no separator is used between words. * See {@link LowerCaseStrategy} for details. * * @since 2.4 @@ -226,8 +242,10 @@ public String nameForConstructorParameter(MapperConfig config, AnnotatedParam * (the first of two underscores was removed) *
  • "user__name" is translated to "user__name" * (unchanged, with two underscores)
  • + * + * @since 2.7 (was previously called } */ - public static class LowerCaseWithUnderscoresStrategy extends PropertyNamingStrategyBase + public static class SnakeCaseStrategy extends PropertyNamingStrategyBase { @Override public String translate(String input) @@ -277,9 +295,9 @@ public String translate(String input) * Java property names to JSON element names. *
    • "userName" is translated to "UserName"
    * - * @since 2.1 + * @since 2.7 (was formerly called {@link PascalCaseStrategy}) */ - public static class PascalCaseStrategy extends PropertyNamingStrategyBase + public static class UpperCamelCaseStrategy extends PropertyNamingStrategyBase { /** * Converts camelCase to PascalCase @@ -322,4 +340,35 @@ public String translate(String input) { return input.toLowerCase(); } } + + /* + /********************************************************** + /* Deprecated variants, aliases + /********************************************************** + */ + + /** + * @deprecated Since 2.7 use {@link #SNAKE_CASE} instead; + */ + @Deprecated // since 2.7 + public static final PropertyNamingStrategy CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES = SNAKE_CASE; + + /** + * @deprecated Since 2.7 use {@link #UPPER_CAMEL_CASE} instead; + */ + @Deprecated // since 2.7 + public static final PropertyNamingStrategy PASCAL_CASE_TO_CAMEL_CASE = UPPER_CAMEL_CASE; + + /** + * @deprecated In 2.7 use {@link SnakeCaseStrategy} instead + */ + @Deprecated + public static class LowerCaseWithUnderscoresStrategy extends SnakeCaseStrategy {} + + /** + * @deprecated In 2.7 use {@link SnakeCaseStrategy} instead + */ + @Deprecated + public static class PascalCaseStrategy extends UpperCamelCaseStrategy {} } + diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index 11bbf82f7a..e6d06c9537 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -224,14 +224,14 @@ public Object findNamingStrategy(AnnotatedClass ac) { JsonNaming ann = _findAnnotation(ac, JsonNaming.class); return (ann == null) ? null : ann.value(); - } + } /* /********************************************************** /* Property auto-detection /********************************************************** */ - + @Override public VisibilityChecker findAutoDetectVisibility(AnnotatedClass ac, VisibilityChecker checker) From f807c8c7cd417d4dd240282fe9510158032bb9fb Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 9 Nov 2015 20:56:28 -0800 Subject: [PATCH 007/124] Fix #963: Add `JsonPropertyNamingStrategy.KEBAB_CASE` for hyphen-delimited (List-style) names --- release-notes/CREDITS | 16 ++- release-notes/VERSION | 2 + .../databind/PropertyNamingStrategy.java | 60 ++++++++- .../TestCreatorWithNamingStrategy556.java | 2 +- .../introspect/TestNamingStrategyStd.java | 117 ++++++++++++------ .../ImplicitParamsForCreator806Test.java | 2 +- 6 files changed, 150 insertions(+), 49 deletions(-) diff --git a/release-notes/CREDITS b/release-notes/CREDITS index b564047364..98c2660c7b 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -359,19 +359,23 @@ Miles Kaufmann (milesk-amzn@github) * Reported #432: `StdValueInstantiator` unwraps exceptions, losing context (2.7.0) +Thomas Mortagne (tmortagne@github) + * Suggested #857: Add support for java.beans.Transient + (2.7.0) + +Jonas Konrad (yawkat@github) + * Suggested #905: Add support for `@ConstructorProperties` + (2.7.0) + Jirka Kremser (Jiri-Kremser@github) * Suggested #924: SequenceWriter.writeAll() could accept Iterable (2.7.0) -Thomas Mortagne (tmortagne@github) - * Suggested #857: Add support for java.beans.Transient +Daniel Mischler (danielmischler@github) + * Requested #963: Add PropertyNameStrategy `KEBAB_CASE` (2.7.0) Shumpei Akai (flexfrank@github) * Reported #978: ObjectMapper#canSerialize(Object.class) returns false even though FAIL_ON_EMPTY_BEANS is disabled (2.7.0) - -Jonas Konrad (yawkat@github) - * Suggested #905: Add support for `@ConstructorProperties` - (2.7.0) diff --git a/release-notes/VERSION b/release-notes/VERSION index 8cb06c9413..e0452c1540 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -36,6 +36,8 @@ Project: jackson-databind (contributed by Jesse W) #957: Merge `datatype-jdk7` stuff in (java.nio.file.Path handling) #959: Schema generation: consider active view, discard non-included properties +#963: Add PropertyNameStrategy `KEBAB_CASE` + (requested by Daniel M) #978: ObjectMapper#canSerialize(Object.class) returns false even though FAIL_ON_EMPTY_BEANS is disabled (reported by Shumpei A) #997: Add `MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS` diff --git a/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java b/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java index cfadb42682..fcd02c57c5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java +++ b/src/main/java/com/fasterxml/jackson/databind/PropertyNamingStrategy.java @@ -13,7 +13,7 @@ * Methods are passed information about POJO member for which name is needed, * as well as default name that would be used if no custom strategy was used. *

    - * Default implementation returns suggested ("default") name unmodified. + * Default (empty) implementation returns suggested ("default") name unmodified. *

    * Note that the strategy is guaranteed to be called once per logical property * (which may be represented by multiple members; such as pair of a getter and @@ -66,7 +66,16 @@ public class PropertyNamingStrategy // NOTE: was abstract until 2.7 * @since 2.4 */ public static final PropertyNamingStrategy LOWER_CASE = new LowerCaseStrategy(); - + + /** + * Naming convention used in languages like Lisp, where words are in lower-case + * letters, separated by hyphens. + * See {@link KebabCaseStrategy} for details. + * + * @since 2.7 + */ + public static final PropertyNamingStrategy KEBAB_CASE = new KebabCaseStrategy(); + /* /********************************************************** /* API @@ -341,6 +350,53 @@ public String translate(String input) { } } + /** + * Naming strategy similar to {@link SnakeCaseStrategy}, but instead of underscores + * as separators, uses hyphens. Naming convention traditionally used for languages + * like Lisp. + * + * @since 2.7 + */ + public static class KebabCaseStrategy extends PropertyNamingStrategyBase + { + @Override + public String translate(String input) + { + if (input == null) return input; // garbage in, garbage out + int length = input.length(); + if (length == 0) { + return input; + } + + StringBuilder result = new StringBuilder(length + (length >> 1)); + + int upperCount = 0; + + for (int i = 0; i < length; ++i) { + char ch = input.charAt(i); + char lc = Character.toLowerCase(ch); + + if (lc == ch) { // lower-case letter means we can get new word + // but need to check for multi-letter upper-case (acronym), where assumption + // is that the last upper-case char is start of a new word + if (upperCount > 1) { + // so insert hyphen before the last character now + result.insert(result.length() - 1, '-'); + } + upperCount = 0; + } else { + // Otherwise starts new word, unless beginning of string + if ((upperCount == 0) && (i > 0)) { + result.append('-'); + } + ++upperCount; + } + result.append(lc); + } + return result.toString(); + } + } + /* /********************************************************** /* Deprecated variants, aliases diff --git a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorWithNamingStrategy556.java b/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorWithNamingStrategy556.java index 0911fcf830..8c4993be2f 100644 --- a/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorWithNamingStrategy556.java +++ b/src/test/java/com/fasterxml/jackson/databind/creators/TestCreatorWithNamingStrategy556.java @@ -58,7 +58,7 @@ public String findImplicitPropertyName(AnnotatedMember param) { } private final ObjectMapper MAPPER = new ObjectMapper() - .setPropertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE) + .setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE) ; { MAPPER.setAnnotationIntrospector(new MyParamIntrospector()); diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java index bed52f9f3f..0c91aeb31a 100644 --- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java @@ -3,25 +3,16 @@ import java.util.Arrays; import java.util.List; -import org.junit.Test; - import com.fasterxml.jackson.annotation.*; -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; + +import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonNaming; import com.fasterxml.jackson.databind.introspect.TestNamingStrategyCustom.PersonBean; import com.fasterxml.jackson.databind.node.ObjectNode; /** - * Unit tests to verify functioning of - * {@link PropertyNamingStrategy#CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES} - * and - * {@link PropertyNamingStrategy#PASCAL_CASE_TO_CAMEL_CASE } - * inside the context of an ObjectMapper. - * PASCAL_CASE_TO_CAMEL_CASE was added in Jackson 2.1, - * as per [JACKSON-63]. + * Unit tests to verify functioning of standard {@link PropertyNamingStrategy} + * implementations Jackson includes out of the box. */ public class TestNamingStrategyStd extends BaseMapTest { @@ -110,14 +101,21 @@ static class ExplicitBean { static class DefaultNaming { public int someValue = 3; } - + + static class FirstNameBean { + public String firstName; + + protected FirstNameBean() { } + public FirstNameBean(String n) { firstName = n; } + } + /* /********************************************************** /* Set up /********************************************************** */ - public static List NAME_TRANSLATIONS = Arrays.asList(new Object[][] { + public static List SNAKE_CASE_NAME_TRANSLATIONS = Arrays.asList(new Object[][] { {null, null}, {"", ""}, {"a", "a"}, @@ -176,32 +174,29 @@ public void setUp() throws Exception { super.setUp(); _lcWithUndescoreMapper = new ObjectMapper(); - _lcWithUndescoreMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); + _lcWithUndescoreMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); } /* /********************************************************** - /* Test methods for CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES + /* Test methods for SNAKE_CASE /********************************************************** */ /** * Unit test to verify translations of - * {@link PropertyNamingStrategy#CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES} + * {@link PropertyNamingStrategy#SNAKE_CASE} * outside the context of an ObjectMapper. - * CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES was added in Jackson 1.9, - * as per [JACKSON-598]. */ - @Test public void testLowerCaseStrategyStandAlone() { - for (Object[] pair : NAME_TRANSLATIONS) { - String translatedJavaName = PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES.nameForField(null, null, + for (Object[] pair : SNAKE_CASE_NAME_TRANSLATIONS) { + String translatedJavaName = PropertyNamingStrategy.SNAKE_CASE.nameForField(null, null, (String) pair[0]); assertEquals((String) pair[1], translatedJavaName); } } - + public void testLowerCaseTranslations() throws Exception { // First serialize @@ -256,34 +251,32 @@ public void testLowerCaseUnchangedNames() throws Exception assertEquals("from7user", result.from7user); assertEquals("_x", result._x); } - + /* /********************************************************** - /* Test methods for PASCAL_CASE_TO_CAMEL_CASE (added in 2.1) + /* Test methods for UPPER_CAMEL_CASE /********************************************************** */ /** * Unit test to verify translations of - * {@link PropertyNamingStrategy#PASCAL_CASE_TO_CAMEL_CASE } + * {@link PropertyNamingStrategy#UPPER_CAMEL_CASE } * outside the context of an ObjectMapper. - * PASCAL_CASE_TO_CAMEL_CASE was added in Jackson 2.1.0, - * as per [JACKSON-63]. */ public void testPascalCaseStandAlone() { - String translatedJavaName = PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE.nameForField + String translatedJavaName = PropertyNamingStrategy.UPPER_CAMEL_CASE.nameForField (null, null, "userName"); assertEquals("UserName", translatedJavaName); - translatedJavaName = PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE.nameForField + translatedJavaName = PropertyNamingStrategy.UPPER_CAMEL_CASE.nameForField (null, null, "User"); assertEquals("User", translatedJavaName); - translatedJavaName = PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE.nameForField + translatedJavaName = PropertyNamingStrategy.UPPER_CAMEL_CASE.nameForField (null, null, "user"); assertEquals("User", translatedJavaName); - translatedJavaName = PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE.nameForField + translatedJavaName = PropertyNamingStrategy.UPPER_CAMEL_CASE.nameForField (null, null, "x"); assertEquals("X", translatedJavaName); } @@ -294,7 +287,7 @@ public void testPascalCaseStandAlone() public void testIssue428PascalWithOverrides() throws Exception { String json = new ObjectMapper() - .setPropertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE) + .setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE) .writeValueAsString(new Bean428()); if (!json.contains(quote("fooBar"))) { @@ -302,6 +295,12 @@ public void testIssue428PascalWithOverrides() throws Exception { } } + /* + /********************************************************** + /* Test methods for LOWER_CASE + /********************************************************** + */ + /** * For [databind#461] */ @@ -314,13 +313,52 @@ public void testSimpleLowerCase() throws Exception m.writeValueAsString(input)); } + /* + /********************************************************** + /* Test methods for KEBAB_CASE + /********************************************************** + */ + + public void testKebabCaseStrategyStandAlone() + { + assertEquals("some-value", + PropertyNamingStrategy.KEBAB_CASE.nameForField(null, null, "someValue")); + assertEquals("some-value", + PropertyNamingStrategy.KEBAB_CASE.nameForField(null, null, "SomeValue")); + assertEquals("url", + PropertyNamingStrategy.KEBAB_CASE.nameForField(null, null, "URL")); + assertEquals("url-stuff", + PropertyNamingStrategy.KEBAB_CASE.nameForField(null, null, "URLStuff")); + assertEquals("some-url-stuff", + PropertyNamingStrategy.KEBAB_CASE.nameForField(null, null, "SomeURLStuff")); + } + + public void testSimpleKebabCase() throws Exception + { + final FirstNameBean input = new FirstNameBean("Bob"); + ObjectMapper m = new ObjectMapper() + .setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE); + + assertEquals(aposToQuotes("{'first-name':'Bob'}"), m.writeValueAsString(input)); + + FirstNameBean result = m.readValue(aposToQuotes("{'first-name':'Billy'}"), + FirstNameBean.class); + assertEquals("Billy", result.firstName); + } + + /* + /********************************************************** + /* Test methods, other + /********************************************************** + */ + /** * Test [databind#815], problems with ObjectNode, naming strategy */ public void testNamingWithObjectNode() throws Exception { - ObjectMapper m = new ObjectMapper(); - m.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE); + ObjectMapper m = new ObjectMapper() + .setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE); ClassWithObjectNodeField result = m.readValue( "{ \"id\": \"1\", \"json\": { \"foo\": \"bar\", \"baz\": \"bing\" } }", @@ -332,16 +370,17 @@ public void testNamingWithObjectNode() throws Exception assertEquals("bing", result.json.path("baz").asText()); } - public void testExplicitRename() throws Exception { + public void testExplicitRename() throws Exception + { ObjectMapper m = new ObjectMapper(); - m.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); + m.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); m.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); // by default, renaming will not take place on explicitly named fields assertEquals(aposToQuotes("{'firstName':'Peter','lastName':'Venkman','user_age':'35'}"), m.writeValueAsString(new ExplicitBean())); m = new ObjectMapper(); - m.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); + m.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); m.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); m.enable(MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING); // w/ feature enabled, ALL property names should get re-written diff --git a/src/test/java/com/fasterxml/jackson/failing/ImplicitParamsForCreator806Test.java b/src/test/java/com/fasterxml/jackson/failing/ImplicitParamsForCreator806Test.java index 031cc14a41..509e572bb5 100644 --- a/src/test/java/com/fasterxml/jackson/failing/ImplicitParamsForCreator806Test.java +++ b/src/test/java/com/fasterxml/jackson/failing/ImplicitParamsForCreator806Test.java @@ -42,7 +42,7 @@ public void testImplicitNameWithNamingStrategy() throws Exception { ObjectMapper mapper = new ObjectMapper() .setAnnotationIntrospector(new MyParamIntrospector()) - .setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES) + .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE) ; XY value = mapper.readValue(aposToQuotes("{'param_name0':1,'param_name1':2}"), XY.class); assertNotNull(value); From df7f407f474369db347a6420ea69c1b00013eb3c Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 11 Nov 2015 22:59:03 -0800 Subject: [PATCH 008/124] Fixed #952 --- release-notes/VERSION | 2 + .../jackson/databind/SerializerProvider.java | 1 - .../databind/ser/BasicSerializerFactory.java | 6 +- .../jackson/databind/ser/PropertyBuilder.java | 73 ++++++- .../ser/std/CollectionSerializer.java | 1 + .../ser/std/NonTypedScalarSerializerBase.java | 4 +- .../databind/ser/std/NumberSerializers.java | 179 ++++++++---------- .../ser/std/StaticListSerializerBase.java | 2 +- .../ser/std/StdDelegatingSerializer.java | 1 + .../databind/ser/std/StringSerializer.java | 6 +- .../databind/ser/std/UUIDSerializer.java | 6 - .../jackson/databind/util/ClassUtil.java | 41 +++- .../databind/filter/JsonIncludeTest.java | 78 +++++++- .../databind/ser/TestJsonSerialize2.java | 47 ----- 14 files changed, 270 insertions(+), 177 deletions(-) diff --git a/release-notes/VERSION b/release-notes/VERSION index e0452c1540..3bd2afdad9 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -34,6 +34,8 @@ Project: jackson-databind (reported by adamjoeldavis@github) #948: Support leap seconds, any number of millisecond digits for ISO-8601 Dates. (contributed by Jesse W) +#952: Revert non-empty handling of primitive numbers wrt `NON_EMPTY`; make + `NON_DEFAULT` use extended criteria #957: Merge `datatype-jdk7` stuff in (java.nio.file.Path handling) #959: Schema generation: consider active view, discard non-included properties #963: Add PropertyNameStrategy `KEBAB_CASE` diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java index 2f28f5e4f6..67d8630f7e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/SerializerProvider.java @@ -8,7 +8,6 @@ import com.fasterxml.jackson.annotation.ObjectIdGenerator; import com.fasterxml.jackson.core.JsonGenerator; - import com.fasterxml.jackson.databind.cfg.ContextAttributes; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.introspect.Annotated; diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java index 425da26e64..56fb912af9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java @@ -818,7 +818,11 @@ protected Object findSuppressableContentValue(SerializationConfig config, return null; case NON_DEFAULT: // 19-Oct-2014, tatu: Not sure what this'd mean; so take it to mean "NON_EMPTY"... - incl = JsonInclude.Include.NON_EMPTY; + // 11-Nov-2015, tatu: With 2.6, we did indeed revert to "NON_EMPTY", but that did + // not go well, so with 2.7, we'll do this instead... + // But not 100% sure if we ought to call new `JsonSerializer.findDefaultValue()`; + // to do that, would need to locate said serializer +// incl = JsonInclude.Include.NON_EMPTY; break; default: // all other modes actually good as is, unless we'll find better ways diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java b/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java index 0cd3b0e182..f89612e80a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/PropertyBuilder.java @@ -32,8 +32,11 @@ public class PropertyBuilder /** * If a property has serialization inclusion value of * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}, - * we need to know the default value of the bean, to know if property value + * we may need to know the default value of the bean, to know if property value * equals default one. + *

    + * NOTE: only used if enclosing class defines NON_DEFAULT, but NOT if it is the + * global default OR per-property override. */ protected Object _defaultBean; @@ -110,7 +113,17 @@ protected BeanPropertyWriter buildWriter(SerializerProvider prov, switch (inclusion) { case NON_DEFAULT: - valueToSuppress = getDefaultValue(propDef.getName(), am); + // 11-Nov-2015, tatu: This is tricky because semantics differ between cases, + // so that if enclosing class has this, we may need to values of property, + // whereas for global defaults OR per-property overrides, we have more + // static definition. Sigh. + // First: case of class specifying it; try to find POJO property defaults + JavaType t = (serializationType == null) ? declaredType : serializationType; + if (_defaultInclusion.getValueInclusion() == JsonInclude.Include.NON_DEFAULT) { + valueToSuppress = getPropertyDefaultValue(propDef.getName(), am, t); + } else { + valueToSuppress = getDefaultValue(t); + } if (valueToSuppress == null) { suppressNulls = true; } else { @@ -118,6 +131,7 @@ protected BeanPropertyWriter buildWriter(SerializerProvider prov, valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress); } } + break; case NON_ABSENT: // new with 2.6, to support Guava/JDK8 Optionals // always suppress nulls @@ -246,17 +260,24 @@ protected Object getDefaultBean() return (def == NO_DEFAULT_MARKER) ? null : _defaultBean; } - protected Object getDefaultValue(String name, AnnotatedMember member) + /** + * Accessor used to find out "default value" for given property, to use for + * comparing values to serialize, to determine whether to exclude value from serialization with + * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_EMPTY}. + * This method is called when we specifically want to know default value within context + * of a POJO, when annotation is within containing class, and not for property or + * defined as global baseline. + *

    + * Note that returning of pseudo-type + * + * @since 2.7 + */ + protected Object getPropertyDefaultValue(String name, AnnotatedMember member, + JavaType type) { Object defaultBean = getDefaultBean(); if (defaultBean == null) { - // 06-Nov-2015, tatu: Returning null is fine for Object types; but need special - // handling for primitives since they are never passed as nulls. - Class cls = member.getRawType(); - if (cls.isPrimitive()) { - return ClassUtil.defaultValue(cls); - } - return null; + return getDefaultValue(type); } try { return member.getValue(defaultBean); @@ -265,6 +286,38 @@ protected Object getDefaultValue(String name, AnnotatedMember member) } } + /** + * Accessor used to find out "default value" to use for comparing values to + * serialize, to determine whether to exclude value from serialization with + * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}. + *

    + * Default logic is such that for primitives and wrapper types for primitives, expected + * defaults (0 for `int` and `java.lang.Integer`) are returned; for Strings, empty String, + * and for structured (Maps, Collections, arrays) and reference types, criteria + * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT} + * is used. + * + * @since 2.7 + */ + protected Object getDefaultValue(JavaType type) + { + // 06-Nov-2015, tatu: Returning null is fine for Object types; but need special + // handling for primitives since they are never passed as nulls. + Class cls = type.getRawClass(); + + Class prim = ClassUtil.primitiveType(cls); + if (prim != null) { + return ClassUtil.defaultValue(prim); + } + if (type.isContainerType() || type.isReferenceType()) { + return JsonInclude.Include.NON_EMPTY; + } + if (cls == String.class) { + return ""; + } + return null; + } + /* /********************************************************** /* Helper methods for exception handling diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java index 139690a38a..7cb18857b7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/CollectionSerializer.java @@ -5,6 +5,7 @@ import java.util.Iterator; import com.fasterxml.jackson.core.*; + import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonSerializer; diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/NonTypedScalarSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/NonTypedScalarSerializerBase.java index 8d5151de90..a0f75a3f29 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/NonTypedScalarSerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/NonTypedScalarSerializerBase.java @@ -26,10 +26,10 @@ protected NonTypedScalarSerializerBase(Class t, boolean bogus) { } @Override - public final void serializeWithType(T value, JsonGenerator jgen, SerializerProvider provider, + public final void serializeWithType(T value, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSer) throws IOException { // no type info, just regular serialization - serialize(value, jgen, provider); + serialize(value, gen, provider); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializers.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializers.java index 05f7b78e9f..bf39be9d3d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializers.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializers.java @@ -5,7 +5,9 @@ import java.util.Map; import com.fasterxml.jackson.annotation.JsonFormat; + import com.fasterxml.jackson.core.*; + import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; @@ -16,15 +18,15 @@ import com.fasterxml.jackson.databind.ser.ContextualSerializer; /** - * Container class for serializers used for handling standard JDK-provided types. + * Container class for serializers used for handling standard JDK-provided + * types. */ @SuppressWarnings("serial") -public class NumberSerializers -{ - protected NumberSerializers() { } +public class NumberSerializers { + protected NumberSerializers() { + } - public static void addAll(Map> allDeserializers) - { + public static void addAll(Map> allDeserializers) { final JsonSerializer intS = new IntegerSerializer(); allDeserializers.put(Integer.class.getName(), intS); allDeserializers.put(Integer.TYPE.getName(), intS); @@ -43,28 +45,24 @@ public static void addAll(Map> allDeserializers) } /* - /********************************************************** - /* Shared base class - /********************************************************** + * /********************************************************** /* Shared + * base class /********************************************************** */ protected abstract static class Base extends StdScalarSerializer - implements ContextualSerializer - { - protected final static Integer EMPTY_INTEGER = Integer.valueOf(0); - + implements ContextualSerializer { protected final JsonParser.NumberType _numberType; protected final String _schemaType; protected final boolean _isInt; - protected Base(Class cls, JsonParser.NumberType numberType, String schemaType) { + protected Base(Class cls, JsonParser.NumberType numberType, + String schemaType) { super(cls, false); _numberType = numberType; _schemaType = schemaType; _isInt = (numberType == JsonParser.NumberType.INT) || (numberType == JsonParser.NumberType.LONG) - || (numberType == JsonParser.NumberType.BIG_INTEGER) - ; + || (numberType == JsonParser.NumberType.BIG_INTEGER); } @Override @@ -73,15 +71,17 @@ public JsonNode getSchema(SerializerProvider provider, Type typeHint) { } @Override - public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException - { + public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, + JavaType typeHint) throws JsonMappingException { if (_isInt) { - JsonIntegerFormatVisitor v2 = visitor.expectIntegerFormat(typeHint); + JsonIntegerFormatVisitor v2 = visitor + .expectIntegerFormat(typeHint); if (v2 != null) { v2.numberType(_numberType); } } else { - JsonNumberFormatVisitor v2 = visitor.expectNumberFormat(typeHint); + JsonNumberFormatVisitor v2 = visitor + .expectNumberFormat(typeHint); if (v2 != null) { v2.numberType(_numberType); } @@ -90,12 +90,12 @@ public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType t @Override public JsonSerializer createContextual(SerializerProvider prov, - BeanProperty property) throws JsonMappingException - { + BeanProperty property) throws JsonMappingException { if (property != null) { AnnotatedMember m = property.getMember(); if (m != null) { - JsonFormat.Value format = prov.getAnnotationIntrospector().findFormat(m); + JsonFormat.Value format = prov.getAnnotationIntrospector() + .findFormat(m); if (format != null) { switch (format.getShape()) { case STRING: @@ -108,74 +108,67 @@ public JsonSerializer createContextual(SerializerProvider prov, return this; } } - + /* - /********************************************************** - /* Concrete serializers, numerics - /********************************************************** + * /********************************************************** /* Concrete + * serializers, numerics + * /********************************************************** */ @JacksonStdImpl - public final static class ShortSerializer extends Base - { - private final static Short EMPTY = (short) 0; + public final static class ShortSerializer extends Base { final static ShortSerializer instance = new ShortSerializer(); - public ShortSerializer() { super(Short.class, JsonParser.NumberType.INT, "number"); } - - @Override - public boolean isEmpty(SerializerProvider prov, Short value) { - return EMPTY.equals(value); + public ShortSerializer() { + super(Short.class, JsonParser.NumberType.INT, "number"); } @Override - public void serialize(Short value, JsonGenerator gen, SerializerProvider provider) throws IOException { - gen.writeNumber(value.shortValue()); + public void serialize(Object value, JsonGenerator gen, + SerializerProvider provider) throws IOException { + gen.writeNumber(((Short) value).shortValue()); } } /** * This is the special serializer for regular {@link java.lang.Integer}s * (and primitive ints) - *

    - * Since this is one of "native" types, no type information is ever - * included on serialization (unlike for most scalar types) - *

    + *

    + * Since this is one of "native" types, no type information is ever included + * on serialization (unlike for most scalar types) + *

    * NOTE: as of 2.6, generic signature changed to Object, to avoid generation * of bridge methods. */ @JacksonStdImpl - public final static class IntegerSerializer extends Base - { - public IntegerSerializer() { super(Integer.class, JsonParser.NumberType.INT ,"integer"); } - + public final static class IntegerSerializer extends Base { + public IntegerSerializer() { + super(Integer.class, JsonParser.NumberType.INT, "integer"); + } + @Override - public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { + public void serialize(Object value, JsonGenerator gen, + SerializerProvider provider) throws IOException { gen.writeNumber(((Integer) value).intValue()); } - + // IMPORTANT: copied from `NonTypedScalarSerializerBase` @Override public void serializeWithType(Object value, JsonGenerator gen, - SerializerProvider provider, TypeSerializer typeSer) throws IOException { + SerializerProvider provider, TypeSerializer typeSer) + throws IOException { // no type info, just regular serialization - serialize(value, gen, provider); - } - - @Override - public boolean isEmpty(SerializerProvider prov, Object value) { - return EMPTY_INTEGER.equals(value); + serialize(value, gen, provider); } } /** * Similar to {@link IntegerSerializer}, but will not cast to Integer: - * instead, cast is to {@link java.lang.Number}, and conversion is - * by calling {@link java.lang.Number#intValue}. + * instead, cast is to {@link java.lang.Number}, and conversion is by + * calling {@link java.lang.Number#intValue}. */ @JacksonStdImpl - public final static class IntLikeSerializer extends Base - { + public final static class IntLikeSerializer extends Base { final static IntLikeSerializer instance = new IntLikeSerializer(); public IntLikeSerializer() { @@ -183,88 +176,70 @@ public IntLikeSerializer() { } @Override - public boolean isEmpty(SerializerProvider prov, Number value) { - return value.intValue() == 0; - } - - @Override - public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException { - gen.writeNumber(value.intValue()); + public void serialize(Object value, JsonGenerator gen, + SerializerProvider provider) throws IOException { + gen.writeNumber(((Number) value).intValue()); } } @JacksonStdImpl - public final static class LongSerializer extends Base - { - private final static Long EMPTY = 0L; - + public final static class LongSerializer extends Base { final static LongSerializer instance = new LongSerializer(); - - public LongSerializer() { super(Long.class, JsonParser.NumberType.LONG, "number"); } - @Override - public boolean isEmpty(SerializerProvider prov, Object value) { - return EMPTY.equals(value); + public LongSerializer() { + super(Long.class, JsonParser.NumberType.LONG, "number"); } @Override - public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { + public void serialize(Object value, JsonGenerator gen, + SerializerProvider provider) throws IOException { gen.writeNumber(((Long) value).longValue()); } } @JacksonStdImpl - public final static class FloatSerializer extends Base - { - private final static Float EMPTY = 0f; - + public final static class FloatSerializer extends Base { final static FloatSerializer instance = new FloatSerializer(); - public FloatSerializer() { super(Float.class, JsonParser.NumberType.FLOAT, "number"); } - - @Override - public boolean isEmpty(SerializerProvider prov, Object value) { - return EMPTY.equals(value); + public FloatSerializer() { + super(Float.class, JsonParser.NumberType.FLOAT, "number"); } @Override - public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { + public void serialize(Object value, JsonGenerator gen, + SerializerProvider provider) throws IOException { gen.writeNumber(((Float) value).floatValue()); } } /** - * This is the special serializer for regular {@link java.lang.Double}s - * (and primitive doubles) - *

    - * Since this is one of "native" types, no type information is ever - * included on serialization (unlike for most scalar types as of 1.5) + * This is the special serializer for regular {@link java.lang.Double}s (and + * primitive doubles) + *

    + * Since this is one of "native" types, no type information is ever included + * on serialization (unlike for most scalar types as of 1.5) */ @JacksonStdImpl - public final static class DoubleSerializer extends Base - { - private final static Double EMPTY = 0d; - + public final static class DoubleSerializer extends Base { final static DoubleSerializer instance = new DoubleSerializer(); - - public DoubleSerializer() { super(Double.class, JsonParser.NumberType.DOUBLE, "number"); } - @Override - public boolean isEmpty(SerializerProvider prov, Object value) { - return EMPTY.equals(value); + public DoubleSerializer() { + super(Double.class, JsonParser.NumberType.DOUBLE, "number"); } @Override - public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { + public void serialize(Object value, JsonGenerator gen, + SerializerProvider provider) throws IOException { gen.writeNumber(((Double) value).doubleValue()); } // IMPORTANT: copied from `NonTypedScalarSerializerBase` @Override public void serializeWithType(Object value, JsonGenerator gen, - SerializerProvider provider, TypeSerializer typeSer) throws IOException { + SerializerProvider provider, TypeSerializer typeSer) + throws IOException { // no type info, just regular serialization - serialize(value, gen, provider); + serialize(value, gen, provider); } } } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java index 9b7e0f4d34..0e40c89bbf 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StaticListSerializerBase.java @@ -111,7 +111,7 @@ public boolean isEmpty(T value) { public boolean isEmpty(SerializerProvider provider, T value) { return (value == null) || (value.size() == 0); } - + @Override public JsonNode getSchema(SerializerProvider provider, Type typeHint) { return createSchemaNode("array", true).set("items", contentSchema()); diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java index 4612bf9a76..067ebc6729 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdDelegatingSerializer.java @@ -1,6 +1,7 @@ package com.fasterxml.jackson.databind.ser.std; import com.fasterxml.jackson.core.JsonGenerator; + import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StringSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StringSerializer.java index 08ff8b0e67..638051c004 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StringSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StringSerializer.java @@ -43,10 +43,10 @@ public boolean isEmpty(SerializerProvider prov, Object value) { String str = (String) value; return (str == null) || (str.length() == 0); } - + @Override - public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - jgen.writeString((String) value); + public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeString((String) value); } @Override diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/UUIDSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/UUIDSerializer.java index 1db68d19e4..cd9e5a1c0e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/UUIDSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/UUIDSerializer.java @@ -22,12 +22,6 @@ public class UUIDSerializer public UUIDSerializer() { super(UUID.class); } - @Override - @Deprecated // since 2.5 - public boolean isEmpty(UUID value) { - return isEmpty(null, value); - } - @Override public boolean isEmpty(SerializerProvider prov, UUID value) { diff --git a/src/main/java/com/fasterxml/jackson/databind/util/ClassUtil.java b/src/main/java/com/fasterxml/jackson/databind/util/ClassUtil.java index 8af8594cd0..3cdcf700d8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/ClassUtil.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/ClassUtil.java @@ -673,7 +673,46 @@ public static Class wrapperType(Class primitiveType) } throw new IllegalArgumentException("Class "+primitiveType.getName()+" is not a primitive type"); } - + + /** + * Method that can be used to find primitive type for given class if (but only if) + * it is either wrapper type or primitive type; returns `null` if type is neither. + * + * @since 2.7 + */ + public static Class primitiveType(Class type) + { + if (type.isPrimitive()) { + return type; + } + + if (type == Integer.class) { + return Integer.TYPE; + } + if (type == Long.class) { + return Long.TYPE; + } + if (type == Boolean.class) { + return Boolean.TYPE; + } + if (type == Double.class) { + return Double.TYPE; + } + if (type == Float.class) { + return Float.TYPE; + } + if (type == Byte.class) { + return Byte.TYPE; + } + if (type == Short.class) { + return Short.TYPE; + } + if (type == Character.class) { + return Character.TYPE; + } + return null; + } + /* /********************************************************** /* Access checking/handling methods diff --git a/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeTest.java b/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeTest.java index 4a6704e8db..db1b1be3e3 100644 --- a/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/filter/JsonIncludeTest.java @@ -59,13 +59,13 @@ static class NonDefaultBeanXYZ } } + @JsonInclude(JsonInclude.Include.NON_DEFAULT) static class MixedBean { String _a = "a", _b = "b"; MixedBean() { } - @JsonInclude(JsonInclude.Include.NON_DEFAULT) public String getA() { return _a; } @JsonInclude(JsonInclude.Include.NON_NULL) @@ -73,16 +73,52 @@ static class MixedBean } // to ensure that default values work for collections as well + @JsonInclude(JsonInclude.Include.NON_DEFAULT) static class ListBean { - @JsonInclude(JsonInclude.Include.NON_DEFAULT) public List strings = new ArrayList(); } - + @JsonInclude(JsonInclude.Include.NON_DEFAULT) static class ArrayBean { public int[] ints = new int[] { 1, 2 }; } + // Test to ensure that default exclusion works for fields too + @JsonPropertyOrder({ "i1", "i2" }) + static class DefaultIntBean { + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public int i1; + + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public Integer i2; + + public DefaultIntBean(int i1, Integer i2) { + this.i1 = i1; + this.i2 = i2; + } + } + + static class NonEmptyString { + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public String value; + + public NonEmptyString(String v) { value = v; } + } + + static class NonEmptyInt { + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public int value; + + public NonEmptyInt(int v) { value = v; } + } + + static class NonEmptyDouble { + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public double value; + + public NonEmptyDouble(double v) { value = v; } + } + /* /********************************************************** /* Unit tests @@ -159,4 +195,40 @@ public void testNonEmptyDefaultArray() throws IOException { assertEquals("{}", MAPPER.writeValueAsString(new ArrayBean())); } + + public void testDefaultForIntegers() throws IOException + { + assertEquals("{}", MAPPER.writeValueAsString(new DefaultIntBean(0, Integer.valueOf(0)))); + assertEquals("{\"i2\":1}", MAPPER.writeValueAsString(new DefaultIntBean(0, Integer.valueOf(1)))); + assertEquals("{\"i1\":3}", MAPPER.writeValueAsString(new DefaultIntBean(3, Integer.valueOf(0)))); + } + + public void testEmptyInclusionScalars() throws IOException + { + ObjectMapper defMapper = MAPPER; + ObjectMapper inclMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + + // First, Strings + StringWrapper str = new StringWrapper(""); + assertEquals("{\"str\":\"\"}", defMapper.writeValueAsString(str)); + assertEquals("{}", inclMapper.writeValueAsString(str)); + assertEquals("{}", inclMapper.writeValueAsString(new StringWrapper())); + + assertEquals("{\"value\":\"x\"}", defMapper.writeValueAsString(new NonEmptyString("x"))); + assertEquals("{}", defMapper.writeValueAsString(new NonEmptyString(""))); + + // Then numbers + // 11-Nov-2015, tatu: As of Jackson 2.7, scalars should NOT be considered empty, + // except for wrappers if they are `null` + assertEquals("{\"value\":12}", defMapper.writeValueAsString(new NonEmptyInt(12))); + assertEquals("{\"value\":0}", defMapper.writeValueAsString(new NonEmptyInt(0))); + + assertEquals("{\"value\":1.25}", defMapper.writeValueAsString(new NonEmptyDouble(1.25))); + assertEquals("{\"value\":0.0}", defMapper.writeValueAsString(new NonEmptyDouble(0.0))); + + + IntWrapper zero = new IntWrapper(0); + assertEquals("{\"i\":0}", defMapper.writeValueAsString(zero)); + assertEquals("{\"i\":0}", inclMapper.writeValueAsString(zero)); + } } diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerialize2.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerialize2.java index 4ab9b269b1..38728c3fa8 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerialize2.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerialize2.java @@ -105,27 +105,6 @@ public MapWrapperWithSerializer(String key, String value) { } } - static class NonEmptyString { - @JsonInclude(JsonInclude.Include.NON_EMPTY) - public String value; - - public NonEmptyString(String v) { value = v; } - } - - static class NonEmptyInt { - @JsonInclude(JsonInclude.Include.NON_EMPTY) - public int value; - - public NonEmptyInt(int v) { value = v; } - } - - static class NonEmptyDouble { - @JsonInclude(JsonInclude.Include.NON_EMPTY) - public double value; - - public NonEmptyDouble(double v) { value = v; } - } - static class NullBean { @JsonSerialize(using=NullSerializer.class) @@ -218,32 +197,6 @@ public void testEmptyInclusionContainers() throws IOException assertEquals("{}", inclMapper.writeValueAsString(new ArrayWrapper(null))); } - public void testEmptyInclusionScalars() throws IOException - { - ObjectMapper defMapper = MAPPER; - ObjectMapper inclMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY); - - // First, Strings - StringWrapper str = new StringWrapper(""); - assertEquals("{\"str\":\"\"}", defMapper.writeValueAsString(str)); - assertEquals("{}", inclMapper.writeValueAsString(str)); - assertEquals("{}", inclMapper.writeValueAsString(new StringWrapper())); - - assertEquals("{\"value\":\"x\"}", defMapper.writeValueAsString(new NonEmptyString("x"))); - assertEquals("{}", defMapper.writeValueAsString(new NonEmptyString(""))); - - // Then numbers - assertEquals("{\"value\":12}", defMapper.writeValueAsString(new NonEmptyInt(12))); - assertEquals("{}", defMapper.writeValueAsString(new NonEmptyInt(0))); - - assertEquals("{\"value\":1.25}", defMapper.writeValueAsString(new NonEmptyDouble(1.25))); - assertEquals("{}", defMapper.writeValueAsString(new NonEmptyDouble(0.0))); - - IntWrapper zero = new IntWrapper(0); - assertEquals("{\"i\":0}", defMapper.writeValueAsString(zero)); - assertEquals("{}", inclMapper.writeValueAsString(zero)); - } - public void testNullSerializer() throws Exception { String json = MAPPER.writeValueAsString(new NullBean()); From 1fcee619c943a23a025e0047742eb075c6382285 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Thu, 12 Nov 2015 23:43:57 -0800 Subject: [PATCH 009/124] Add unit test for #1002, improved javadocs as well --- .../databind/SerializationFeature.java | 10 +++- .../objectid/TestObjectIdWithEquals.java | 57 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java b/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java index 17e2582338..fe40906a91 100644 --- a/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java +++ b/src/main/java/com/fasterxml/jackson/databind/SerializationFeature.java @@ -387,7 +387,15 @@ public enum SerializationFeature implements ConfigFeature * Feature that determines whether Object Identity is compared using * true JVM-level identity of Object (false); or, equals() method. * Latter is sometimes useful when dealing with Database-bound objects with - * ORM libraries (like Hibernate). + * ORM libraries (like Hibernate). Note that Object itself is actually compared, + * and NOT Object Id; naming of this feature is somewhat confusing, so it is important + * that Object for which identity is to be preserved are considered equal, + * above and beyond ids (which are always compared using equality anyway). + *

    + * NOTE: due to the way functionality is implemented, it is very important that + * in addition to overriding {@link Object#equals} for Objects to match (to be considered + * "same") it is also necessary to ensure that {@link Object#hashCode()} is overridden + * to produce the exact same value for equal instances. *

    * Feature is disabled by default; meaning that strict identity is used, not * equals() diff --git a/src/test/java/com/fasterxml/jackson/databind/objectid/TestObjectIdWithEquals.java b/src/test/java/com/fasterxml/jackson/databind/objectid/TestObjectIdWithEquals.java index 3b57c4b99a..ea02709fbc 100644 --- a/src/test/java/com/fasterxml/jackson/databind/objectid/TestObjectIdWithEquals.java +++ b/src/test/java/com/fasterxml/jackson/databind/objectid/TestObjectIdWithEquals.java @@ -1,8 +1,10 @@ package com.fasterxml.jackson.databind.objectid; +import java.net.URI; import java.util.*; import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.*; public class TestObjectIdWithEquals extends BaseMapTest @@ -42,6 +44,34 @@ public boolean equals(Object obj) { } } + // for [databind#1002] + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class") + @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "uri") + static class Element { + public URI uri; + public String name; + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } else if (object == null || !(object instanceof Element)) { + return false; + } else { + Element element = (Element) object; + if (element.uri.toString().equalsIgnoreCase(this.uri.toString())) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + return uri.hashCode(); + } + } + /* /****************************************************** /* Test methods @@ -77,4 +107,31 @@ public void testSimpleEquals() throws Exception assertNotNull(foo2); assertEquals(foo.id, foo2.id); } + + public void testEqualObjectIdsExternal() throws Exception + { + Element element = new Element(); + element.uri = URI.create("URI"); + element.name = "Element1"; + + Element element2 = new Element(); + element2.uri = URI.create("URI"); + element2.name = "Element2"; + + // 12-Nov-2015, tatu: array works fine regardless of Type Erasure, but if using List, + // must provide additional piece of type info +// Element[] input = new Element[] { element, element2 }; + List input = Arrays.asList(element, element2); + + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(SerializationFeature.USE_EQUALITY_FOR_OBJECT_ID); + +// String json = mapper.writeValueAsString(input); + String json = mapper.writerFor(new TypeReference>() { }) + .writeValueAsString(input); + + Element[] output = mapper.readValue(json, Element[].class); + assertNotNull(output); + assertEquals(2, output.length); + } } From cc8b4a5c745724f7b0e8eecee9c727502b7ec28a Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 15 Nov 2015 20:24:58 -0800 Subject: [PATCH 010/124] Add support for `@JsonClassDescription` via `AnnotationIntrospector.findClassDescription()`, `BeanDescription.findClassDescription()` --- release-notes/VERSION | 5 +++-- .../databind/AnnotationIntrospector.java | 13 +++++++++++ .../jackson/databind/BeanDescription.java | 12 ++++++++-- .../AnnotationIntrospectorPair.java | 9 ++++++++ .../introspect/BasicBeanDescription.java | 6 +++++ .../JacksonAnnotationIntrospector.java | 6 +++++ .../introspect/BeanDescriptionTest.java | 22 +++++++++++++++++++ .../TestPOJOPropertiesCollector.java | 5 ++--- 8 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/introspect/BeanDescriptionTest.java diff --git a/release-notes/VERSION b/release-notes/VERSION index 3bd2afdad9..d21e0f79ed 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -14,10 +14,10 @@ Project: jackson-databind (reported by Miles K) #497: Add new JsonInclude.Include feature to exclude maps after exclusion removes all elements #819: Add support for setting `FormatFeature` via `ObjectReader`, `ObjectWriter` -#857: Add support for java.beans.Transient +#857: Add support for java.beans.Transient (requires Java 7) (suggested by Thomas M) #898: Add `ObjectMapper.getSerializerProviderInstance()` -#905: Add support for `@ConstructorProperties` +#905: Add support for `@ConstructorProperties` (requires Java 7) (requested by Jonas K) #909: Rename PropertyNamingStrategy CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES as SNAKE_CASE, PASCAL_CASE_TO_CAMEL_CASE as UPPER_CAMEL_CASE @@ -51,6 +51,7 @@ INCOMPATIBILITIES: - While unlikely to be problematic, #959 above required an addition of `SerializerProvider` argument for `depositSchemaProperty()` method `BeanProperty` and `PropertyWriter` interfaces +- JDK baseline now Java 7 (JDK 1.7), from Java 6/JDK 1.6 2.6.4 (not yet released) diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index fa6e7c0ae2..9b46fc54b5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -283,6 +283,19 @@ public String[] findPropertiesToIgnore(Annotated ac) { */ public Object findNamingStrategy(AnnotatedClass ac) { return null; } + /** + * Method used to check whether specified class defines a human-readable + * description to use for documentation. + * There are no further definitions for contents; for example, whether + * these may be marked up using HTML (or something like wiki format like Markup) + * is not defined. + * + * @return Human-readable description, if any. + * + * @since 2.7 + */ + public String findClassDescription(AnnotatedClass ac) { return null; } + /* /********************************************************** /* Property auto-detection diff --git a/src/main/java/com/fasterxml/jackson/databind/BeanDescription.java b/src/main/java/com/fasterxml/jackson/databind/BeanDescription.java index a620430ae7..dc7838ff33 100644 --- a/src/main/java/com/fasterxml/jackson/databind/BeanDescription.java +++ b/src/main/java/com/fasterxml/jackson/databind/BeanDescription.java @@ -90,13 +90,13 @@ protected BeanDescription(JavaType type) { * generic type resolution context. */ public abstract JavaType resolveType(java.lang.reflect.Type jdkType); - + /** * Method for accessing collection of annotations the bean * class has. */ public abstract Annotations getClassAnnotations(); - + /* /********************************************************** /* Basic API for finding properties @@ -217,6 +217,14 @@ protected BeanDescription(JavaType type) { */ public abstract Converter findDeserializationConverter(); + /** + * Accessor for possible description for the bean type, used for constructing + * documentation. + * + * @since 2.7 + */ + public String findClassDescription() { return null; } + /* /********************************************************** /* Basic API, other diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java index a000e6b27e..af38f4403d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java @@ -172,6 +172,15 @@ public Object findNamingStrategy(AnnotatedClass ac) return str; } + @Override + public String findClassDescription(AnnotatedClass ac) { + String str = _primary.findClassDescription(ac); + if ((str == null) || str.isEmpty()) { + str = _secondary.findClassDescription(ac); + } + return str; + } + /* /****************************************************** /* Property auto-detection diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java index 04f88235fa..7bfd7f47c8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicBeanDescription.java @@ -620,6 +620,12 @@ public Converter findDeserializationConverter() return _createConverter(_annotationIntrospector.findDeserializationConverter(_classInfo)); } + @Override + public String findClassDescription() { + return (_annotationIntrospector == null) ? + null : _annotationIntrospector.findClassDescription(_classInfo); + } + /* /********************************************************** /* Helper methods for field introspection diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index e6d06c9537..ccc20e75a6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -226,6 +226,12 @@ public Object findNamingStrategy(AnnotatedClass ac) return (ann == null) ? null : ann.value(); } + @Override + public String findClassDescription(AnnotatedClass ac) { + JsonClassDescription ann = _findAnnotation(ac, JsonClassDescription.class); + return (ann == null) ? null : ann.value(); + } + /* /********************************************************** /* Property auto-detection diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/BeanDescriptionTest.java b/src/test/java/com/fasterxml/jackson/databind/introspect/BeanDescriptionTest.java new file mode 100644 index 0000000000..3d75f65435 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/BeanDescriptionTest.java @@ -0,0 +1,22 @@ +package com.fasterxml.jackson.databind.introspect; + +import com.fasterxml.jackson.annotation.JsonClassDescription; +import com.fasterxml.jackson.databind.*; + +public class BeanDescriptionTest extends BaseMapTest +{ + private final ObjectMapper MAPPER = objectMapper(); + + private final static String CLASS_DESC = "Description, yay!"; + + @JsonClassDescription(CLASS_DESC) + static class DocumentedBean { + public int x; + } + + public void testClassDesc() throws Exception + { + BeanDescription beanDesc = MAPPER.getDeserializationConfig().introspect(MAPPER.constructType(DocumentedBean.class)); + assertEquals(CLASS_DESC, beanDesc.findClassDescription()); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java index eafc0d0a37..e621ed4400 100644 --- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TestPOJOPropertiesCollector.java @@ -412,7 +412,6 @@ public void testSimpleWithType() assertEquals(String.class, m.getRawType()); } - // for [JACKSON-701] public void testInnerClassWithAnnotationsInCreator() throws Exception { BasicBeanDescription beanDesc; @@ -424,9 +423,9 @@ public void testInnerClassWithAnnotationsInCreator() throws Exception assertNotNull(beanDesc); } - public void testJackson703() throws Exception + public void testUseAnnotationsFalse() throws Exception { - // note: need a separate mapper, need to reconfigure + // note: need a separate mapper, need to reconfigure ObjectMapper mapper = new ObjectMapper(); mapper.configure(MapperFeature.USE_ANNOTATIONS, false); BasicBeanDescription beanDesc = mapper.getSerializationConfig().introspect(mapper.constructType(Jackson703.class)); From 391e90f6ed291beac71b29dabe873dd856e47ee7 Mon Sep 17 00:00:00 2001 From: Hugo Wood Date: Wed, 18 Nov 2015 00:37:49 +0100 Subject: [PATCH 011/124] Experimental support for array delegate creator --- .../databind/deser/BeanDeserializerBase.java | 2 +- .../databind/deser/ValueInstantiator.java | 9 ++++ .../databind/deser/impl/CreatorCollector.java | 33 +++++++++++-- .../deser/std/StdValueInstantiator.java | 49 +++++++++++++++++++ .../TestObjectOrArrayDeserialization.java | 47 ++++++++++++++++++ 5 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/deser/TestObjectOrArrayDeserialization.java diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java index ad585cd5a2..e2e3d91336 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -1225,7 +1225,7 @@ public Object deserializeFromArray(JsonParser p, DeserializationContext ctxt) th { if (_delegateDeserializer != null) { try { - Object bean = _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(p, ctxt)); + Object bean = _valueInstantiator.createUsingArrayDelegate(ctxt, _delegateDeserializer.deserialize(p, ctxt)); if (_injectables != null) { injectValues(ctxt, bean); } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java index 95dab9b13d..73a261e689 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/ValueInstantiator.java @@ -171,6 +171,15 @@ public Object createUsingDelegate(DeserializationContext ctxt, Object delegate) getValueTypeDesc()); } + /** + * Method to called to create value instance from JSON Array using + * an intermediate "delegate" value to pass to createor method + */ + public Object createUsingArrayDelegate(DeserializationContext ctxt, Object delegate) throws IOException { + throw ctxt.mappingException("Can not instantiate value of type %s using delegate", + getValueTypeDesc()); + } + /* /********************************************************** /* Instantiation methods for JSON scalar types diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java index c5b4800062..0ea13fdaf4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java @@ -28,6 +28,7 @@ public class CreatorCollector protected final static int C_BOOLEAN = 5; protected final static int C_DELEGATE = 6; protected final static int C_PROPS = 7; + protected final static int C_ARRAY_DELEGATE = 8; protected final static String[] TYPE_DESCS = new String[] { "default", @@ -50,7 +51,7 @@ public class CreatorCollector * * @since 2.5 */ - protected final AnnotatedWithParams[] _creators = new AnnotatedWithParams[8]; + protected final AnnotatedWithParams[] _creators = new AnnotatedWithParams[9]; /** * Bitmask of creators that were explicitly marked as creators; false for auto-detected @@ -65,6 +66,8 @@ public class CreatorCollector // when there are injectable values along with delegate: protected SettableBeanProperty[] _delegateArgs; + protected SettableBeanProperty[] _arrayDelegateArgs; + protected SettableBeanProperty[] _propertyBasedArgs; protected AnnotatedParameter _incompleteParameter; @@ -103,6 +106,24 @@ public ValueInstantiator constructValueInstantiator(DeserializationConfig config delegateType = _creators[C_DELEGATE].getParameterType(ix); } + JavaType arrayDelegateType; + + if (maybeVanilla || (_creators[C_ARRAY_DELEGATE] == null)) { + arrayDelegateType = null; + } else { + // need to find type... + int ix = 0; + if (_arrayDelegateArgs != null) { + for (int i = 0, len = _arrayDelegateArgs.length; i < len; ++i) { + if (_arrayDelegateArgs[i] == null) { // marker for delegate itself + ix = i; + break; + } + } + } + arrayDelegateType = _creators[C_ARRAY_DELEGATE].getParameterType(ix); + } + final JavaType type = _beanDesc.getType(); // Any non-standard creator will prevent; with one exception: int-valued constructor @@ -129,6 +150,7 @@ public ValueInstantiator constructValueInstantiator(DeserializationConfig config inst.configureFromObjectSettings(_creators[C_DEFAULT], _creators[C_DELEGATE], delegateType, _delegateArgs, _creators[C_PROPS], _propertyBasedArgs); + inst.configureFromArraySettings(_creators[C_ARRAY_DELEGATE], arrayDelegateType, _arrayDelegateArgs); inst.configureFromStringCreator(_creators[C_STRING]); inst.configureFromIntCreator(_creators[C_INT]); inst.configureFromLongCreator(_creators[C_LONG]); @@ -176,8 +198,13 @@ public void addBooleanCreator(AnnotatedWithParams creator, boolean explicit) { public void addDelegatingCreator(AnnotatedWithParams creator, boolean explicit, SettableBeanProperty[] injectables) { - verifyNonDup(creator, C_DELEGATE, explicit); - _delegateArgs = injectables; + if (creator.getParameterType(0).isCollectionLikeType()) { + verifyNonDup(creator, C_ARRAY_DELEGATE, explicit); + _arrayDelegateArgs = injectables; + } else { + verifyNonDup(creator, C_DELEGATE, explicit); + _delegateArgs = injectables; + } } public void addPropertyCreator(AnnotatedWithParams creator, boolean explicit, diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java index 5daee786c7..41e0914f43 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java @@ -45,6 +45,12 @@ public class StdValueInstantiator protected JavaType _delegateType; protected AnnotatedWithParams _delegateCreator; protected SettableBeanProperty[] _delegateArguments; + + // // // Array delegate construction + + protected JavaType _arrayDelegateType; + protected AnnotatedWithParams _arrayDelegateCreator; + protected SettableBeanProperty[] _arrayDelegateArguments; // // // Scalar construction @@ -87,6 +93,10 @@ protected StdValueInstantiator(StdValueInstantiator src) _delegateType = src._delegateType; _delegateCreator = src._delegateCreator; _delegateArguments = src._delegateArguments; + + _arrayDelegateType = src._arrayDelegateType; + _arrayDelegateCreator = src._arrayDelegateCreator; + _arrayDelegateArguments = src._arrayDelegateArguments; _fromStringCreator = src._fromStringCreator; _fromIntCreator = src._fromIntCreator; @@ -112,6 +122,16 @@ public void configureFromObjectSettings(AnnotatedWithParams defaultCreator, _constructorArguments = constructorArgs; } + public void configureFromArraySettings( + AnnotatedWithParams arrayDelegateCreator, + JavaType arrayDelegateType, + SettableBeanProperty[] arrayDelegateArgs) + { + _arrayDelegateCreator = arrayDelegateCreator; + _arrayDelegateType = arrayDelegateType; + _arrayDelegateArguments = arrayDelegateArgs; + } + public void configureFromStringCreator(AnnotatedWithParams creator) { _fromStringCreator = creator; } @@ -257,6 +277,35 @@ public Object createUsingDelegate(DeserializationContext ctxt, Object delegate) throw rewrapCtorProblem(ctxt, t); } } + + @Override + public Object createUsingArrayDelegate(DeserializationContext ctxt, Object delegate) throws IOException + { + if (_arrayDelegateCreator == null) { // sanity-check; caller should check + throw new IllegalStateException("No delegate constructor for "+getValueTypeDesc()); + } + try { + // First simple case: just delegate, no injectables + if (_arrayDelegateArguments == null) { + return _arrayDelegateCreator.call1(delegate); + } + // And then the case with at least one injectable... + final int len = _arrayDelegateArguments.length; + Object[] args = new Object[len]; + for (int i = 0; i < len; ++i) { + SettableBeanProperty prop = _arrayDelegateArguments[i]; + if (prop == null) { // delegate + args[i] = delegate; + } else { // nope, injectable: + args[i] = ctxt.findInjectableValue(prop.getInjectableValueId(), prop, null); + } + } + // and then try calling with full set of arguments + return _arrayDelegateCreator.call(args); + } catch (Throwable t) { + throw rewrapCtorProblem(ctxt, t); + } + } /* /********************************************************** diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestObjectOrArrayDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestObjectOrArrayDeserialization.java new file mode 100644 index 0000000000..8eedd7b789 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestObjectOrArrayDeserialization.java @@ -0,0 +1,47 @@ +package com.fasterxml.jackson.databind.deser; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.List; +import java.util.Map; + +public class TestObjectOrArrayDeserialization extends BaseMapTest { + + public static class ArrayOrObject { + private final List objects; + private final Object object; + + @JsonCreator public ArrayOrObject(List objects) { + this.objects = objects; + this.object = null; + } + + @JsonCreator public ArrayOrObject(Object object) { + this.objects = null; + this.object = object; + } + } + + public void testObjectCase() throws Exception { + ArrayOrObject arrayOrObject = objectMapper().readValue("{}", ArrayOrObject.class); + assertNull("expected objects field to be null", arrayOrObject.objects); + assertNotNull("expected object field not to be null", arrayOrObject.object); + } + + public void testEmptyArrayCase() throws Exception { + ArrayOrObject arrayOrObject = objectMapper().readValue("[]", ArrayOrObject.class); + assertNotNull("expected objects field not to be null", arrayOrObject.objects); + assertTrue("expected objects field to be an empty list", arrayOrObject.objects.isEmpty()); + assertNull("expected object field to be null", arrayOrObject.object); + } + + public void testNotEmptyArrayCase() throws Exception { + ArrayOrObject arrayOrObject = objectMapper().readValue("[{}, {}]", ArrayOrObject.class); + assertNotNull("expected objects field not to be null", arrayOrObject.objects); + assertEquals("expected objects field to have size 2", 2, arrayOrObject.objects.size()); + assertNull("expected object field to be null", arrayOrObject.object); + } + +} From 95ac6b3e9d0b3eb4a276f6dea2b19459bcad6631 Mon Sep 17 00:00:00 2001 From: Hugo Wood Date: Wed, 18 Nov 2015 23:12:47 +0100 Subject: [PATCH 012/124] createUsingArrayDelegate should fallback to the class createUsingDelegate if there's no array delegate set --- .../jackson/databind/deser/std/StdValueInstantiator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java index 41e0914f43..6968c5dea5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java @@ -282,7 +282,8 @@ public Object createUsingDelegate(DeserializationContext ctxt, Object delegate) public Object createUsingArrayDelegate(DeserializationContext ctxt, Object delegate) throws IOException { if (_arrayDelegateCreator == null) { // sanity-check; caller should check - throw new IllegalStateException("No delegate constructor for "+getValueTypeDesc()); + // try falling back to the classic delegate + return createUsingDelegate(ctxt, delegate); } try { // First simple case: just delegate, no injectables From 45b40547bd6f61a95dba7dcdaea052c385b08291 Mon Sep 17 00:00:00 2001 From: Hugo Wood Date: Wed, 18 Nov 2015 23:16:18 +0100 Subject: [PATCH 013/124] Using fresh mapper for each test --- .../databind/deser/TestObjectOrArrayDeserialization.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestObjectOrArrayDeserialization.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestObjectOrArrayDeserialization.java index 8eedd7b789..0efc75d20f 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestObjectOrArrayDeserialization.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestObjectOrArrayDeserialization.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.databind.BaseMapTest; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; import java.util.Map; @@ -25,20 +26,20 @@ public static class ArrayOrObject { } public void testObjectCase() throws Exception { - ArrayOrObject arrayOrObject = objectMapper().readValue("{}", ArrayOrObject.class); + ArrayOrObject arrayOrObject = new ObjectMapper().readValue("{}", ArrayOrObject.class); assertNull("expected objects field to be null", arrayOrObject.objects); assertNotNull("expected object field not to be null", arrayOrObject.object); } public void testEmptyArrayCase() throws Exception { - ArrayOrObject arrayOrObject = objectMapper().readValue("[]", ArrayOrObject.class); + ArrayOrObject arrayOrObject = new ObjectMapper().readValue("[]", ArrayOrObject.class); assertNotNull("expected objects field not to be null", arrayOrObject.objects); assertTrue("expected objects field to be an empty list", arrayOrObject.objects.isEmpty()); assertNull("expected object field to be null", arrayOrObject.object); } public void testNotEmptyArrayCase() throws Exception { - ArrayOrObject arrayOrObject = objectMapper().readValue("[{}, {}]", ArrayOrObject.class); + ArrayOrObject arrayOrObject = new ObjectMapper().readValue("[{}, {}]", ArrayOrObject.class); assertNotNull("expected objects field not to be null", arrayOrObject.objects); assertEquals("expected objects field to have size 2", 2, arrayOrObject.objects.size()); assertNull("expected object field to be null", arrayOrObject.object); From 1ed7f3834dc77cf76d7477e8165dedb8ae2d61c2 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 20 Nov 2015 13:41:09 -0800 Subject: [PATCH 014/124] Fixed #1005 for 2.6.4 --- release-notes/CREDITS | 4 ++++ release-notes/VERSION | 2 ++ .../databind/introspect/AnnotatedClass.java | 24 ++++++++++++------- .../databind/type/TestAnnotatedClass.java | 17 +++++++++++++ 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 9ad5393149..8dd633944a 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -346,3 +346,7 @@ Warren Bloomer (stormboy@github) Ievgen Pianov (pyanoveugen@github) * Reported #989: Deserialization from "{}" to java.lang.Object causes "out of END_OBJECT token" error (2.6.3) + +Jayson Minard (apatrida@github) + * Reported #1005: Synthetic constructors confusing Jackson data binding + (2.6.4) diff --git a/release-notes/VERSION b/release-notes/VERSION index ddda92c615..051e12933a 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -10,6 +10,8 @@ Project: jackson-databind (reported by Antibrumm@github) #989: Deserialization from "{}" to java.lang.Object causes "out of END_OBJECT token" error (reported by Ievgen P) +#1005: Synthetic constructors confusing Jackson data binding + (reported by Jayson M) - Fix a minor problem with `@JsonNaming` not recognizing default value 2.6.3 (12-Oct-2015) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java index d548f7c1fe..61151a9642 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java @@ -336,13 +336,15 @@ private void resolveCreators() List constructors = null; Constructor[] declaredCtors = _class.getDeclaredConstructors(); for (Constructor ctor : declaredCtors) { - if (ctor.getParameterTypes().length == 0) { - _defaultConstructor = _constructConstructor(ctor, true); - } else { - if (constructors == null) { - constructors = new ArrayList(Math.max(10, declaredCtors.length)); + if (_isIncludableConstructor(ctor)) { + if (ctor.getParameterTypes().length == 0) { + _defaultConstructor = _constructConstructor(ctor, true); + } else { + if (constructors == null) { + constructors = new ArrayList(Math.max(10, declaredCtors.length)); + } + constructors.add(_constructConstructor(ctor, false)); } - constructors.add(_constructConstructor(ctor, false)); } } if (constructors == null) { @@ -863,9 +865,7 @@ protected boolean _isIncludableMemberMethod(Method m) private boolean _isIncludableField(Field f) { - /* I'm pretty sure synthetic fields are to be skipped... - * (methods definitely are) - */ + // Most likely synthetic fields, if any, are to be skipped similar to methods if (f.isSynthetic()) { return false; } @@ -878,6 +878,12 @@ private boolean _isIncludableField(Field f) return true; } + // for [databind#1005]: do not use or expose synthetic constructors + private boolean _isIncludableConstructor(Constructor c) + { + return !c.isSynthetic(); + } + /* /********************************************************** /* Helper methods, attaching annotations diff --git a/src/test/java/com/fasterxml/jackson/databind/type/TestAnnotatedClass.java b/src/test/java/com/fasterxml/jackson/databind/type/TestAnnotatedClass.java index df551d995f..786374a943 100644 --- a/src/test/java/com/fasterxml/jackson/databind/type/TestAnnotatedClass.java +++ b/src/test/java/com/fasterxml/jackson/databind/type/TestAnnotatedClass.java @@ -65,6 +65,12 @@ static class FieldBean private String props; } + // for [databind#1005] + static class Bean1005 { + // private to force creation of a synthetic constructor to avoid access issues + private Bean1005(int i) {} + } + /* /********************************************************** /* Test methods @@ -84,4 +90,15 @@ public void testFieldIntrospection() } } } + + // For [databind#1005] + public void testConstructorIntrospection() + { + // Need this call to ensure there is a synthetic constructor being generated + // (not really needed otherwise) + Bean1005 bean = new Bean1005(13); + AnnotatedClass ac = AnnotatedClass.construct(bean.getClass(), + new JacksonAnnotationIntrospector(), null); + assertEquals(1, ac.getConstructors().size()); + } } From 660ec8f8c081646413d14adf41d55bbd4362a5a8 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 20 Nov 2015 14:02:01 -0800 Subject: [PATCH 015/124] Fix #1013 --- release-notes/CREDITS | 4 +++ release-notes/VERSION | 2 ++ .../JacksonAnnotationIntrospector.java | 9 +++++- .../databind/struct/TestUnwrapped.java | 31 +++++++++++++++++-- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 8dd633944a..87b6b2251e 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -350,3 +350,7 @@ Ievgen Pianov (pyanoveugen@github) Jayson Minard (apatrida@github) * Reported #1005: Synthetic constructors confusing Jackson data binding (2.6.4) + +David Bakin (david-bakin@github) + * Reported #1013: `@JsonUnwrapped` is not treated as assuming `@JsonProperty("")` + (2.6.4) diff --git a/release-notes/VERSION b/release-notes/VERSION index 051e12933a..cee298bdeb 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -12,6 +12,8 @@ Project: jackson-databind (reported by Ievgen P) #1005: Synthetic constructors confusing Jackson data binding (reported by Jayson M) +#1013: `@JsonUnwrapped` is not treated as assuming `@JsonProperty("")` + (reported by David B) - Fix a minor problem with `@JsonNaming` not recognizing default value 2.6.3 (12-Oct-2015) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index 829fedd693..5db1584c12 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -739,9 +739,16 @@ public PropertyName findNameForSerialization(Annotated a) JsonProperty pann = _findAnnotation(a, JsonProperty.class); if (pann != null) { name = pann.value(); + /* 22-Apr-2014, tatu: Should figure out a better way to do this, but + * it's actually bit tricky to do it more efficiently (meta-annotations + * add more lookups; AnnotationMap costs etc) + */ } else if (_hasAnnotation(a, JsonSerialize.class) || _hasAnnotation(a, JsonView.class) - || _hasAnnotation(a, JsonRawValue.class)) { + || _hasAnnotation(a, JsonRawValue.class) + || _hasAnnotation(a, JsonUnwrapped.class) + || _hasAnnotation(a, JsonBackReference.class) + || _hasAnnotation(a, JsonManagedReference.class)) { name = ""; } else { return null; diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrapped.java b/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrapped.java index 86574d6c34..59ac4e08fc 100644 --- a/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrapped.java +++ b/src/test/java/com/fasterxml/jackson/databind/struct/TestUnwrapped.java @@ -5,7 +5,9 @@ import com.fasterxml.jackson.databind.*; /** - * Unit tests for verifying [JACKSON-132] implementation. + * Unit tests for verifying that basic {@link JsonUnwrapped} annotation + * handling works as expected; some more advanced tests are separated out + * to more specific test classes (like prefix/suffix handling). */ public class TestUnwrapped extends BaseMapTest { @@ -85,6 +87,16 @@ public Child() { } public Child(String f) { field = f; } } + static class Inner { + public String animal; + } + + static class Outer { + // @JsonProperty + @JsonUnwrapped + private Inner inner; + } + /* /********************************************************** /* Tests, serialization @@ -166,11 +178,26 @@ public void testIssue615() throws Exception assertEquals("name", output.c1.field); } + public void testUnwrappedAsPropertyIndicator() throws Exception + { + Inner inner = new Inner(); + inner.animal = "Zebra"; + + Outer outer = new Outer(); + outer.inner = inner; + + String actual = MAPPER.writeValueAsString(outer); + + assertTrue(actual.contains("animal")); + assertTrue(actual.contains("Zebra")); + assertFalse(actual.contains("inner")); + } + // 22-Apr-2013, tatu: Commented out as it can't be simply fixed; requires implementing // deep-update/merge. But leaving here to help with that effort, if/when it proceeds. /* - // [Issue#211]: Actually just variant of #160 + // [databind#211]: Actually just variant of #160 static class Issue211Bean { public String test1; From da2f2c9743c2614b9104d0b25982dab6cfd60165 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 20 Nov 2015 14:18:48 -0800 Subject: [PATCH 016/124] Fix #1011 --- release-notes/CREDITS | 2 ++ release-notes/VERSION | 2 ++ .../fasterxml/jackson/databind/ObjectReader.java | 2 +- .../fasterxml/jackson/databind/ObjectWriter.java | 5 ++++- .../jackson/databind/cfg/ContextAttributes.java | 16 ++++++++++------ .../jackson/databind/cfg/MapperConfigBase.java | 2 +- 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 104ba356c3..f95c5258cf 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -362,6 +362,8 @@ Jayson Minard (apatrida@github) David Bakin (david-bakin@github) * Reported #1013: `@JsonUnwrapped` is not treated as assuming `@JsonProperty("")` (2.6.4) + * Suggested #1011: Change ObjectWriter::withAttributes() to take a Map with some kind of wildcard types + (2.7.0) Miles Kaufmann (milesk-amzn@github) * Reported #432: `StdValueInstantiator` unwraps exceptions, losing context diff --git a/release-notes/VERSION b/release-notes/VERSION index 60e6f1ff2f..7343372fd0 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -44,6 +44,8 @@ Project: jackson-databind (reported by Shumpei A) #997: Add `MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS` #998: Allow use of `NON_DEFAULT` for POJOs without default constructor +#1011: Change ObjectWriter::withAttributes() to take a Map with some kind of wildcard types + (suggested by David B) - Make `JsonValueFormat` (self-)serializable, deserializable, to/from valid external value (as per JSON Schema spec) diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java index ccbb05f062..60e37dca01 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java @@ -836,7 +836,7 @@ public ObjectReader with(ContextAttributes attrs) { /** * @since 2.3 */ - public ObjectReader withAttributes(Map attrs) { + public ObjectReader withAttributes(Map attrs) { return _with(_config.withAttributes(attrs)); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectWriter.java b/src/main/java/com/fasterxml/jackson/databind/ObjectWriter.java index f419da41ac..1758925d05 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectWriter.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectWriter.java @@ -631,9 +631,12 @@ public ObjectWriter with(ContextAttributes attrs) { } /** + * Mutant factory method that allows construction of a new writer instance + * that uses specified set of default attribute values. + * * @since 2.3 */ - public ObjectWriter withAttributes(Map attrs) { + public ObjectWriter withAttributes(Map attrs) { SerializationConfig newConfig = _config.withAttributes(attrs); return (newConfig == _config) ? this : _new(this, newConfig); } diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/ContextAttributes.java b/src/main/java/com/fasterxml/jackson/databind/cfg/ContextAttributes.java index 577bd48522..5eca263a8a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/ContextAttributes.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/ContextAttributes.java @@ -32,7 +32,7 @@ public static ContextAttributes getEmpty() { public abstract ContextAttributes withSharedAttribute(Object key, Object value); - public abstract ContextAttributes withSharedAttributes(Map attributes); + public abstract ContextAttributes withSharedAttributes(Map attributes); public abstract ContextAttributes withoutSharedAttribute(Object key); @@ -71,11 +71,15 @@ public static class Impl extends ContextAttributes /** * Shared attributes that we can not modify in-place. */ - protected final Map _shared; + protected final Map _shared; /** * Per-call attributes that we can directly modify, since they are not * shared between threads. + *

    + * NOTE: typed as Object-to-Object, unlike {@link #_shared}, because + * we need to be able to modify contents, and wildcard type would + * complicate that access. */ protected transient Map _nonShared; @@ -85,12 +89,12 @@ public static class Impl extends ContextAttributes /********************************************************** */ - protected Impl(Map shared) { + protected Impl(Map shared) { _shared = shared; _nonShared = null; } - protected Impl(Map shared, Map nonShared) { + protected Impl(Map shared, Map nonShared) { _shared = shared; _nonShared = nonShared; } @@ -120,7 +124,7 @@ public ContextAttributes withSharedAttribute(Object key, Object value) } @Override - public ContextAttributes withSharedAttributes(Map shared) { + public ContextAttributes withSharedAttributes(Map shared) { return new Impl(shared); } @@ -207,7 +211,7 @@ protected ContextAttributes nonSharedInstance(Object key, Object value) return new Impl(_shared, m); } - private Map _copy(Map src) + private Map _copy(Map src) { return new HashMap(src); } diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java index ef202b738c..11513ec15b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java @@ -379,7 +379,7 @@ public T withRootName(String rootName) { * * @since 2.3 */ - public T withAttributes(Map attributes) { + public T withAttributes(Map attributes) { return with(getAttributes().withSharedAttributes(attributes)); } From f4dd7663f4543d0bcb8f42735c79a25747addc49 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 20 Nov 2015 21:38:14 -0800 Subject: [PATCH 017/124] ... --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7b7b55f715..195e0cc9c4 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ com.fasterxml.jackson.core jackson-annotations - 2.7.0-SNAPSHOT + 2.7.0-rc1 com.fasterxml.jackson.core From f5988593e8ace7ea01fcb54e124cece2c5d19401 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 20 Nov 2015 21:54:50 -0800 Subject: [PATCH 018/124] Add a failing test for #1001 --- ...elegatingCreatorImplicitNames1001Test.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/test/java/com/fasterxml/jackson/failing/DelegatingCreatorImplicitNames1001Test.java diff --git a/src/test/java/com/fasterxml/jackson/failing/DelegatingCreatorImplicitNames1001Test.java b/src/test/java/com/fasterxml/jackson/failing/DelegatingCreatorImplicitNames1001Test.java new file mode 100644 index 0000000000..2de303eb1f --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/failing/DelegatingCreatorImplicitNames1001Test.java @@ -0,0 +1,96 @@ +package com.fasterxml.jackson.failing; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.introspect.AnnotatedMember; +import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; +import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; +import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams; +import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; + +public class DelegatingCreatorImplicitNames1001Test extends BaseMapTest +{ + static class D + { + private String raw1 = ""; + private String raw2 = ""; + + private D(String raw1, String raw2) { + this.raw1 = raw1; + this.raw2 = raw2; + } + + // not needed strictly speaking, but added for good measure + @JsonCreator(mode=JsonCreator.Mode.DELEGATING) + public static D make(String value) { + String[] split = value.split(":"); + return new D(split[0], split[1]); + } + + @JsonValue + public String getMyValue() { + return raw1 + ":" + raw2; + } + + @Override + public String toString() { + return getMyValue(); + } + + @Override + public boolean equals(Object o) { + D other = (D) o; + return other.raw1.equals(raw1) + && other.raw2.equals(raw2); + } + } + + // To test equivalent of parameter-names, let's use this one + protected static class CreatorNameIntrospector extends JacksonAnnotationIntrospector + { + private static final long serialVersionUID = 1L; + + @Override + public String findImplicitPropertyName(AnnotatedMember member) { + if (member instanceof AnnotatedParameter) { + AnnotatedParameter p = (AnnotatedParameter) member; + AnnotatedWithParams owner = p.getOwner(); + if (owner instanceof AnnotatedMethod) { + if (p.getIndex() == 0) { + return "value"; + } + } + } + return super.findImplicitPropertyName(member); + } + } + + // Baseline test to show how things should work + public void testWithoutNamedParameters() throws Exception + { + ObjectMapper sut = new ObjectMapper(); + + D d = D.make("abc:def"); + + String actualJson = sut.writeValueAsString(d); + D actualD = sut.readValue(actualJson, D.class); + + assertEquals("\"abc:def\"", actualJson); + assertEquals(d, actualD); + } + + // And then case that fails with [databind#1001] + public void testWithNamedParameters() throws Exception + { + ObjectMapper sut = new ObjectMapper() + .setAnnotationIntrospector(new CreatorNameIntrospector()); + + D d = D.make("abc:def"); + + String actualJson = sut.writeValueAsString(d); + D actualD = sut.readValue(actualJson, D.class); + + assertEquals("\"abc:def\"", actualJson); + assertEquals(d, actualD); + } +} From 16bddc108c4c3890542ef91cd84ab6eeefc5447e Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 21 Nov 2015 10:23:42 -0800 Subject: [PATCH 019/124] Start work on #1000, UUIDDeserializer will now throw `InvalidFormatException` --- .../deser/std/FromStringDeserializer.java | 18 +++--- .../databind/deser/std/UUIDDeserializer.java | 58 +++++++++++-------- .../databind/exc/InvalidFormatException.java | 2 +- .../databind/deser/TestSimpleTypes.java | 19 ++++++ 4 files changed, 64 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java index 10123529a9..609e61e287 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java @@ -98,20 +98,20 @@ public static Std findDeserializer(Class rawType) @SuppressWarnings("unchecked") @Override - public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException + public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { // Issue#381 - if (jp.getCurrentToken() == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { - jp.nextToken(); - final T value = deserialize(jp, ctxt); - if (jp.nextToken() != JsonToken.END_ARRAY) { - throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY, + if (p.getCurrentToken() == JsonToken.START_ARRAY && ctxt.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)) { + p.nextToken(); + final T value = deserialize(p, ctxt); + if (p.nextToken() != JsonToken.END_ARRAY) { + throw ctxt.wrongTokenException(p, JsonToken.END_ARRAY, "Attempted to unwrap single value array for single '" + _valueClass.getName() + "' value but there was more than a single value in the array"); } return value; } // 22-Sep-2012, tatu: For 2.1, use this new method, may force coercion: - String text = jp.getValueAsString(); + String text = p.getValueAsString(); if (text != null) { // has String representation if (text.length() == 0 || (text = text.trim()).length() == 0) { // 04-Feb-2013, tatu: Usually should become null; but not always @@ -140,9 +140,9 @@ public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOExcept throw e; // nothing to do here, yet? We'll fail anyway } - if (jp.getCurrentToken() == JsonToken.VALUE_EMBEDDED_OBJECT) { + if (p.getCurrentToken() == JsonToken.VALUE_EMBEDDED_OBJECT) { // Trivial cases; null to null, instance of type itself returned as is - Object ob = jp.getEmbeddedObject(); + Object ob = p.getEmbeddedObject(); if (ob == null) { return null; } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java index 01f969c9ea..03f3bf63fb 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java @@ -5,8 +5,10 @@ import java.util.UUID; import com.fasterxml.jackson.core.Base64Variants; - +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; public class UUIDDeserializer extends FromStringDeserializer { @@ -37,24 +39,24 @@ protected UUID _deserialize(String id, DeserializationContext ctxt) throws IOExc byte[] stuff = Base64Variants.getDefaultVariant().decode(id); return _fromBytes(stuff, ctxt); } - _badFormat(id); + _badFormat(id, ctxt); } // verify hyphens first: if ((id.charAt(8) != '-') || (id.charAt(13) != '-') || (id.charAt(18) != '-') || (id.charAt(23) != '-')) { - _badFormat(id); + _badFormat(id, ctxt); } - long l1 = intFromChars(id, 0); + long l1 = intFromChars(id, 0, ctxt); l1 <<= 32; - long l2 = ((long) shortFromChars(id, 9)) << 16; - l2 |= shortFromChars(id, 14); + long l2 = ((long) shortFromChars(id, 9, ctxt)) << 16; + l2 |= shortFromChars(id, 14, ctxt); long hi = l1 + l2; - int i1 = (shortFromChars(id, 19) << 16) | shortFromChars(id, 24); + int i1 = (shortFromChars(id, 19, ctxt) << 16) | shortFromChars(id, 24, ctxt); l1 = i1; l1 <<= 32; - l2 = intFromChars(id, 28); + l2 = intFromChars(id, 28, ctxt); l2 = (l2 << 32) >>> 32; // sign removal, Java-style. Ugh. long lo = l1 | l2; @@ -71,19 +73,26 @@ protected UUID _deserializeEmbedded(Object ob, DeserializationContext ctxt) thro return null; // never gets here } - private void _badFormat(String uuidStr) { - throw new NumberFormatException("UUID has to be represented by the standard 36-char representation"); + private void _badFormat(String uuidStr, DeserializationContext ctxt) + throws JsonMappingException + { + throw InvalidFormatException.from(ctxt.getParser(), + "UUID has to be represented by standard 36-char representation", + uuidStr, handledType()); } - static int intFromChars(String str, int index) { - return (byteFromChars(str, index) << 24) + (byteFromChars(str, index+2) << 16) + (byteFromChars(str, index+4) << 8) + byteFromChars(str, index+6); + static int intFromChars(String str, int index, DeserializationContext ctxt) throws JsonMappingException { + return (byteFromChars(str, index, ctxt) << 24) + + (byteFromChars(str, index+2, ctxt) << 16) + + (byteFromChars(str, index+4, ctxt) << 8) + + byteFromChars(str, index+6, ctxt); } - static int shortFromChars(String str, int index) { - return (byteFromChars(str, index) << 8) + byteFromChars(str, index+2); + static int shortFromChars(String str, int index, DeserializationContext ctxt) throws JsonMappingException { + return (byteFromChars(str, index, ctxt) << 8) + byteFromChars(str, index+2, ctxt); } - static int byteFromChars(String str, int index) + static int byteFromChars(String str, int index, DeserializationContext ctxt) throws JsonMappingException { final char c1 = str.charAt(index); final char c2 = str.charAt(index+1); @@ -95,20 +104,23 @@ static int byteFromChars(String str, int index) } } if (c1 > 127 || HEX_DIGITS[c1] < 0) { - return _badChar(str, index, c1); + return _badChar(str, index, ctxt, c1); } - return _badChar(str, index+1, c2); + return _badChar(str, index+1, ctxt, c2); } - static int _badChar(String uuidStr, int index, char c) { - throw new NumberFormatException("Non-hex character '"+c+"', not valid character for a UUID String" - +"' (value 0x"+Integer.toHexString(c)+") for UUID String \""+uuidStr+"\""); + static int _badChar(String uuidStr, int index, DeserializationContext ctxt, char c) throws JsonMappingException { + String msg = String.format( +"Non-hex character '%c' (value 0x%s), not valid for UUID String: input String '%s'", + c, Integer.toHexString(c), uuidStr); + throw InvalidFormatException.from(ctxt.getParser(), msg, uuidStr, UUID.class); } - private UUID _fromBytes(byte[] bytes, DeserializationContext ctxt) throws IOException { + private UUID _fromBytes(byte[] bytes, DeserializationContext ctxt) throws JsonMappingException { if (bytes.length != 16) { - ctxt.mappingException("Can only construct UUIDs from byte[16]; got %d bytes", - bytes.length); + throw InvalidFormatException.from(ctxt.getParser(), + "Can only construct UUIDs from byte[16]; got "+bytes.length+" bytes", + bytes, handledType()); } return new UUID(_long(bytes, 0), _long(bytes, 8)); } diff --git a/src/main/java/com/fasterxml/jackson/databind/exc/InvalidFormatException.java b/src/main/java/com/fasterxml/jackson/databind/exc/InvalidFormatException.java index 771fd76f0c..e7593d1451 100644 --- a/src/main/java/com/fasterxml/jackson/databind/exc/InvalidFormatException.java +++ b/src/main/java/com/fasterxml/jackson/databind/exc/InvalidFormatException.java @@ -67,7 +67,7 @@ public InvalidFormatException(JsonParser p, _value = value; _targetType = targetType; } - + public static InvalidFormatException from(JsonParser p, String msg, Object value, Class targetType) { diff --git a/src/test/java/com/fasterxml/jackson/databind/deser/TestSimpleTypes.java b/src/test/java/com/fasterxml/jackson/databind/deser/TestSimpleTypes.java index 4500453a85..8fe48cf9b6 100644 --- a/src/test/java/com/fasterxml/jackson/databind/deser/TestSimpleTypes.java +++ b/src/test/java/com/fasterxml/jackson/databind/deser/TestSimpleTypes.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.fasterxml.jackson.databind.util.TokenBuffer; /** @@ -628,6 +629,24 @@ public void testUUID() throws Exception mapper.readValue(quote(base64), UUID.class)); } + public void testUUIDInvalid() throws Exception + { + // and finally, exception handling too [databind#1000], for invalid cases + try { + MAPPER.readValue(quote("abcde"), UUID.class); + fail("Should fail on invalid UUID string"); + } catch (InvalidFormatException e) { + verifyException(e, "UUID has to be represented by standard"); + } + try { + MAPPER.readValue(quote("76e6d183-5f68-4afa-b94a-922c1fdb83fx"), UUID.class); + fail("Should fail on invalid UUID string"); + } catch (InvalidFormatException e) { + verifyException(e, "non-hex character 'x'"); + } + // should also test from-bytes version, but that's trickier... leave for now. + } + public void testUUIDAux() throws Exception { // [JACKSON-393] fix: From dc87afa9d3c564b8fd8ba7f949d9a33350bdad97 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 21 Nov 2015 10:25:30 -0800 Subject: [PATCH 020/124] (addition to #1000 work) --- .../fasterxml/jackson/databind/deser/std/UUIDDeserializer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java index 03f3bf63fb..8854fd5778 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java @@ -77,7 +77,8 @@ private void _badFormat(String uuidStr, DeserializationContext ctxt) throws JsonMappingException { throw InvalidFormatException.from(ctxt.getParser(), - "UUID has to be represented by standard 36-char representation", + String.format("UUID has to be represented by standard 36-char representation: input String '%s'", + uuidStr), uuidStr, handledType()); } From 20a67f73f362bce0d0d93afacec12d30796affa8 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 21 Nov 2015 14:17:45 -0800 Subject: [PATCH 021/124] Fix #1000; now UUID and Enum deserializers should throw `InvalidFormatException` as expected --- release-notes/VERSION | 2 ++ .../databind/DeserializationContext.java | 29 +++++++++++++++---- .../databind/deser/std/EnumDeserializer.java | 26 ++++++++++------- .../deser/std/StdKeyDeserializer.java | 2 +- .../databind/convert/TestBeanConversions.java | 2 +- 5 files changed, 43 insertions(+), 18 deletions(-) diff --git a/release-notes/VERSION b/release-notes/VERSION index 7343372fd0..9e691f7b45 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -44,6 +44,8 @@ Project: jackson-databind (reported by Shumpei A) #997: Add `MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS` #998: Allow use of `NON_DEFAULT` for POJOs without default constructor +#1000: Add new mapping exception type for enums and UUIDs + (suggesed by natnan@github) #1011: Change ObjectWriter::withAttributes() to take a Map with some kind of wildcard types (suggested by David B) - Make `JsonValueFormat` (self-)serializable, deserializable, to/from valid external diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java index 0c091d9767..72114f2b47 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationContext.java @@ -905,8 +905,8 @@ public JsonMappingException instantiationException(Class instClass, String ms */ public JsonMappingException weirdStringException(String value, Class instClass, String msg) { return InvalidFormatException.from(_parser, - String.format("Can not construct instance of %s from String value '%s': %s", - instClass.getName(), _valueDesc(), msg), + String.format("Can not construct instance of %s from String value (%s): %s", + instClass.getName(), _quotedString(value), msg), value, instClass); } @@ -917,8 +917,8 @@ public JsonMappingException weirdStringException(String value, Class instClas public JsonMappingException weirdNumberException(Number value, Class instClass, String msg) { return InvalidFormatException.from(_parser, String.format("Can not construct instance of %s from number value (%s): %s", - instClass.getName(), _valueDesc(), msg), - null, instClass); + instClass.getName(), String.valueOf(value), msg), + value, instClass); } /** @@ -928,8 +928,8 @@ public JsonMappingException weirdNumberException(Number value, Class instClas */ public JsonMappingException weirdKeyException(Class keyClass, String keyValue, String msg) { return InvalidFormatException.from(_parser, - String.format("Can not construct Map key of type %s from String \"%s\": ", - keyClass.getName(), _desc(keyValue), msg), + String.format("Can not construct Map key of type %s from String (%s): %s", + keyClass.getName(), _quotedString(keyValue), msg), keyValue, keyClass); } @@ -1019,10 +1019,27 @@ protected String _valueDesc() { } protected String _desc(String desc) { + if (desc == null) { + return "[N/A]"; + } // !!! should we quote it? (in case there are control chars, linefeeds) if (desc.length() > MAX_ERROR_STR_LEN) { desc = desc.substring(0, MAX_ERROR_STR_LEN) + "]...[" + desc.substring(desc.length() - MAX_ERROR_STR_LEN); } return desc; } + + // @since 2.7 + protected String _quotedString(String desc) { + if (desc == null) { + return "[N/A]"; + } + // !!! should we quote it? (in case there are control chars, linefeeds) + if (desc.length() > MAX_ERROR_STR_LEN) { + return String.format("\"%s]...[%s\"", + desc.substring(0, MAX_ERROR_STR_LEN), + desc.substring(desc.length() - MAX_ERROR_STR_LEN)); + } + return "\"" + desc + "\""; + } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java index d7777f8d55..b077484dfb 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/EnumDeserializer.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.util.ClassUtil; @@ -88,15 +89,16 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx } // But let's consider int acceptable as well (if within ordinal range) if (curr == JsonToken.VALUE_NUMBER_INT) { - // ... unless told not to do that. :-) (as per [JACKSON-412]) - _checkFailOnNumber(ctxt); - + // ... unless told not to do that int index = p.getIntValue(); + if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) { + _failOnNumber(ctxt, p, index); + } if (index >= 0 && index <= _enumsByIndex.length) { return _enumsByIndex[index]; } if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { - throw ctxt.weirdNumberException(Integer.valueOf(index), _enumClass(), + throw ctxt.weirdNumberException(index, _enumClass(), "index value outside legal index range [0.."+(_enumsByIndex.length-1)+"]"); } return null; @@ -113,12 +115,14 @@ private final Object _deserializeAltString(JsonParser p, DeserializationContext return null; } } else { - // [#149]: Allow use of 'String' indexes as well + // [databind#149]: Allow use of 'String' indexes as well char c = name.charAt(0); if (c >= '0' && c <= '9') { try { int ix = Integer.parseInt(name); - _checkFailOnNumber(ctxt); + if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) { + _failOnNumber(ctxt, p, ix); + } if (ix >= 0 && ix <= _enumsByIndex.length) { return _enumsByIndex[ix]; } @@ -152,11 +156,13 @@ protected Object _deserializeOther(JsonParser p, DeserializationContext ctxt) th throw ctxt.mappingException(_enumClass()); } - protected void _checkFailOnNumber(DeserializationContext ctxt) throws IOException + protected void _failOnNumber(DeserializationContext ctxt, JsonParser p, int index) + throws IOException { - if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) { - throw ctxt.mappingException("Not allowed to deserialize Enum value out of JSON number (disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow)"); - } + throw InvalidFormatException.from(p, + String.format("Not allowed to deserialize Enum value out of JSON number (%d): disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow", + index), + index, _enumClass()); } protected Class _enumClass() { diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java index c3195cc5ee..c2d752e2e3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdKeyDeserializer.java @@ -339,7 +339,7 @@ public Object _parse(String key, DeserializationContext ctxt) throws JsonMapping } } Enum e = _resolver.findEnum(key); - if (e == null && !ctxt.getConfig().isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { + if ((e == null) && !ctxt.getConfig().isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { throw ctxt.weirdKeyException(_keyClass, key, "not one of values for Enum class"); } return e; diff --git a/src/test/java/com/fasterxml/jackson/databind/convert/TestBeanConversions.java b/src/test/java/com/fasterxml/jackson/databind/convert/TestBeanConversions.java index 189d01ee5d..3baee2038a 100644 --- a/src/test/java/com/fasterxml/jackson/databind/convert/TestBeanConversions.java +++ b/src/test/java/com/fasterxml/jackson/databind/convert/TestBeanConversions.java @@ -124,7 +124,7 @@ public void testErrorReporting() throws Exception try { MAPPER.readValue("{\"boolProp\":\"foobar\"}", BooleanBean.class); } catch (JsonMappingException e) { - verifyException(e, "from String value 'foobar'"); + verifyException(e, "from String value (\"foobar\")"); } } From c4ad251ef7e5d9e646ed4ea5351bc272e9c236a9 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 24 Nov 2015 20:10:17 -0800 Subject: [PATCH 022/124] Fixed #1016 --- .../databind/deser/std/UUIDDeserializer.java | 2 +- .../JacksonAnnotationIntrospector.java | 100 +++++++++++++++--- 2 files changed, 87 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java index 8854fd5778..5fa5e11d58 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/UUIDDeserializer.java @@ -5,7 +5,7 @@ import java.util.UUID; import com.fasterxml.jackson.core.Base64Variants; -import com.fasterxml.jackson.core.JsonParser; + import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.exc.InvalidFormatException; diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index 4236b9d640..c9116330b3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -32,6 +32,19 @@ public class JacksonAnnotationIntrospector { private static final long serialVersionUID = 1L; + private static final Java7Support _jdk7Helper; + static { + Java7Support x = null; + try { + x = Java7Support.class.newInstance(); + } catch (Throwable t) { + // 24-Nov-2015, tatu: Should we log or not? + java.util.logging.Logger.getLogger(JacksonAnnotationIntrospector.class.getName()) + .warning("Unable to load JDK7 annotation types; will have to skip"); + } + _jdk7Helper = x; + } + /** * Since introspection of annotation types is a performance issue in some * use cases (rare, but do exist), let's try a simple cache to reduce @@ -999,11 +1012,11 @@ public boolean hasCreatorAnnotation(Annotated a) return (ann.mode() != JsonCreator.Mode.DISABLED); } if (a instanceof AnnotatedConstructor) { - ConstructorProperties props = _findAnnotation(a, ConstructorProperties.class); - // 08-Nov-2015, tatu: One possible check would be to ensure there is at least - // one name iff constructor has arguments. But seems unnecessary for now. - if (props != null) { - return true; + if (_jdk7Helper != null) { + Boolean b = _jdk7Helper.hasCreatorAnnotation(a); + if (b != null) { + return b.booleanValue(); + } } } return false; @@ -1027,9 +1040,11 @@ protected boolean _isIgnorable(Annotated a) if (ann != null) { return ann.value(); } - Transient t = _findAnnotation(a, Transient.class); - if (t != null) { - return t.value(); + if (_jdk7Helper != null) { + Boolean b = _jdk7Helper.findTransient(a); + if (b != null) { + return b.booleanValue(); + } } return false; } @@ -1063,12 +1078,10 @@ protected PropertyName _findConstructorName(Annotated a) AnnotatedWithParams ctor = p.getOwner(); if (ctor != null) { - ConstructorProperties props = _findAnnotation(ctor, ConstructorProperties.class); - if (props != null) { - String[] names = props.value(); - int ix = p.getIndex(); - if (ix < names.length) { - return PropertyName.construct(names[ix]); + if (_jdk7Helper != null) { + PropertyName name = _jdk7Helper.findConstructorName(p); + if (name != null) { + return name; } } } @@ -1154,4 +1167,63 @@ protected StdTypeResolverBuilder _constructStdTypeResolverBuilder() { protected StdTypeResolverBuilder _constructNoTypeResolverBuilder() { return StdTypeResolverBuilder.noTypeInfoBuilder(); } + + /* + /********************************************************** + /* Helper classes + /********************************************************** + */ + + /** + * To support Java7-incomplete platforms, we will offer support for JDK 7 + * annotations through this class, loaded dynamically; if loading fails, + * support will be missing. + */ + private static class Java7Support + { + @SuppressWarnings("unused") // compiler warns, just needed side-effects + private final Class _bogus; + + @SuppressWarnings("unused") // compiler warns; called via Reflection + public Java7Support() { + // Trigger loading of annotations that only JDK 7 has... + Class cls = Transient.class; + cls = ConstructorProperties.class; + _bogus = cls; + } + + public Boolean findTransient(Annotated a) { + Transient t = a.getAnnotation(Transient.class); + if (t != null) { + return t.value(); + } + return null; + } + + public Boolean hasCreatorAnnotation(Annotated a) { + ConstructorProperties props = a.getAnnotation(ConstructorProperties.class); + // 08-Nov-2015, tatu: One possible check would be to ensure there is at least + // one name iff constructor has arguments. But seems unnecessary for now. + if (props != null) { + return Boolean.TRUE; + } + return null; + } + + public PropertyName findConstructorName(AnnotatedParameter p) + { + AnnotatedWithParams ctor = p.getOwner(); + if (ctor != null) { + ConstructorProperties props = ctor.getAnnotation(ConstructorProperties.class); + if (props != null) { + String[] names = props.value(); + int ix = p.getIndex(); + if (ix < names.length) { + return PropertyName.construct(names[ix]); + } + } + } + return null; + } + } } From 4a8e9b4279131ee3504119c9d94c732776058abe Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 24 Nov 2015 20:44:35 -0800 Subject: [PATCH 023/124] Add a failing test for #1003 --- .../DelegatingExternalProperty1003Test.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/test/java/com/fasterxml/jackson/failing/DelegatingExternalProperty1003Test.java diff --git a/src/test/java/com/fasterxml/jackson/failing/DelegatingExternalProperty1003Test.java b/src/test/java/com/fasterxml/jackson/failing/DelegatingExternalProperty1003Test.java new file mode 100644 index 0000000000..9de595dcf2 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/failing/DelegatingExternalProperty1003Test.java @@ -0,0 +1,56 @@ +package com.fasterxml.jackson.failing; + +import com.fasterxml.jackson.annotation.*; + +import com.fasterxml.jackson.databind.*; + +public class DelegatingExternalProperty1003Test extends BaseMapTest +{ + static class HeroBattle { + + private final Hero hero; + + private HeroBattle(Hero hero) { + if (hero == null) throw new Error(); + this.hero = hero; + } + + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "heroType") + public Hero getHero() { + return hero; + } + + @JsonCreator + static HeroBattle fromJson(Delegate json) { + return new HeroBattle(json.hero); + } + } + + static class Delegate { + @JsonProperty + @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "heroType") + public Hero hero; + } + + public interface Hero { } + + static class Superman implements Hero { + public String getName() { + return "superman"; + } + } + + public void testExtrnalPropertyDelegatingCreator() throws Exception + { + ObjectMapper mapper = new ObjectMapper(); + + final String json = mapper.writeValueAsString(new HeroBattle(new Superman())); + +//System.err.println("JSON: "+json); + final HeroBattle battle = mapper.readValue(json, HeroBattle.class); + + assert battle.getHero() instanceof Superman; + } + + +} From f54f0f1f64903c5b479b0b42d2d0db6f308d0f31 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 24 Nov 2015 20:52:01 -0800 Subject: [PATCH 024/124] Add temporary exception for unsupported (as of yet) combo of delegating creator, external type id --- .../jackson/databind/deser/BeanDeserializer.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 2dafe1b862..f67487d79a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -716,6 +716,10 @@ protected Object deserializeWithExternalTypeId(JsonParser p, DeserializationCont if (_propertyBasedCreator != null) { return deserializeUsingPropertyBasedWithExternalTypeId(p, ctxt); } + if (_delegateDeserializer != null) { + return deserializeUsingDelegateWithExternalTypeId(p, ctxt); + } + return deserializeWithExternalTypeId(p, ctxt, _valueInstantiator.createUsingDefault(ctxt)); } @@ -856,4 +860,13 @@ protected Object deserializeUsingPropertyBasedWithExternalTypeId(JsonParser p, D return null; // never gets here } } + + /** + * @since 2.7 + */ + protected Object deserializeUsingDelegateWithExternalTypeId(JsonParser p, DeserializationContext ctxt) + throws IOException + { + throw ctxt.mappingException("Combination of External Type Id, Delegating Creator not yet supported"); + } } From 19ba37248e23156b7898b198d77ee75e6ec6c06e Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 24 Nov 2015 21:13:46 -0800 Subject: [PATCH 025/124] minor streamlining --- .../databind/deser/BeanDeserializer.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index f67487d79a..2d22e2a337 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -549,7 +549,7 @@ protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext c } continue; } - // ignorable things should be ignored + // Things marked as ignorable should not be passed to any setter if (_ignorableProps != null && _ignorableProps.contains(propName)) { handleIgnoredProperty(p, ctxt, bean, propName); continue; @@ -672,10 +672,7 @@ protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p, Deseri buffer.bufferProperty(prop, _deserializeWithErrorWrapping(p, ctxt, prop)); continue; } - - /* As per [JACKSON-313], things marked as ignorable should not be - * passed to any setter - */ + // Things marked as ignorable should not be passed to any setter if (_ignorableProps != null && _ignorableProps.contains(propName)) { handleIgnoredProperty(p, ctxt, handledType(), propName); continue; @@ -839,9 +836,7 @@ protected Object deserializeUsingPropertyBasedWithExternalTypeId(JsonParser p, D if (ext.handlePropertyValue(p, ctxt, propName, null)) { continue; } - /* As per [JACKSON-313], things marked as ignorable should not be - * passed to any setter - */ + // Things marked as ignorable should not be passed to any setter if (_ignorableProps != null && _ignorableProps.contains(propName)) { handleIgnoredProperty(p, ctxt, handledType(), propName); continue; @@ -867,6 +862,14 @@ protected Object deserializeUsingPropertyBasedWithExternalTypeId(JsonParser p, D protected Object deserializeUsingDelegateWithExternalTypeId(JsonParser p, DeserializationContext ctxt) throws IOException { - throw ctxt.mappingException("Combination of External Type Id, Delegating Creator not yet supported"); + // 24-Nov-2015, tatu: Something along these lines would normally work, in absence + // of external type id: + /* + Object delegate = _delegateDeserializer.deserialize(p, ctxt); + return _valueInstantiator.createUsingDelegate(ctxt, delegate); + */ + + throw ctxt.instantiationException(handledType(), + "Combination of External Type Id, Delegating Creator not yet supported"); } } From be1136361aa2eb9ddf65f9af48dda36abc4a4cf0 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 24 Nov 2015 21:43:30 -0800 Subject: [PATCH 026/124] Fix #1003 --- .../databind/deser/BeanDeserializer.java | 25 ++++++------------- .../DelegatingExternalProperty1003Test.java | 11 ++++---- 2 files changed, 12 insertions(+), 24 deletions(-) rename src/test/java/com/fasterxml/jackson/{failing => databind/creators}/DelegatingExternalProperty1003Test.java (88%) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 2d22e2a337..5ca7d2e830 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -714,7 +714,13 @@ protected Object deserializeWithExternalTypeId(JsonParser p, DeserializationCont return deserializeUsingPropertyBasedWithExternalTypeId(p, ctxt); } if (_delegateDeserializer != null) { - return deserializeUsingDelegateWithExternalTypeId(p, ctxt); + /* 24-Nov-2015, tatu: Use of delegating creator needs to have precedence, and basically + * external type id handling just has to be ignored, as they would relate to target + * type and not delegate type. Whether this works as expected is another story, but + * there's no other way to really mix these conflicting features. + */ + return _valueInstantiator.createUsingDelegate(ctxt, + _delegateDeserializer.deserialize(p, ctxt)); } return deserializeWithExternalTypeId(p, ctxt, _valueInstantiator.createUsingDefault(ctxt)); @@ -855,21 +861,4 @@ protected Object deserializeUsingPropertyBasedWithExternalTypeId(JsonParser p, D return null; // never gets here } } - - /** - * @since 2.7 - */ - protected Object deserializeUsingDelegateWithExternalTypeId(JsonParser p, DeserializationContext ctxt) - throws IOException - { - // 24-Nov-2015, tatu: Something along these lines would normally work, in absence - // of external type id: - /* - Object delegate = _delegateDeserializer.deserialize(p, ctxt); - return _valueInstantiator.createUsingDelegate(ctxt, delegate); - */ - - throw ctxt.instantiationException(handledType(), - "Combination of External Type Id, Delegating Creator not yet supported"); - } } diff --git a/src/test/java/com/fasterxml/jackson/failing/DelegatingExternalProperty1003Test.java b/src/test/java/com/fasterxml/jackson/databind/creators/DelegatingExternalProperty1003Test.java similarity index 88% rename from src/test/java/com/fasterxml/jackson/failing/DelegatingExternalProperty1003Test.java rename to src/test/java/com/fasterxml/jackson/databind/creators/DelegatingExternalProperty1003Test.java index 9de595dcf2..2060d81b21 100644 --- a/src/test/java/com/fasterxml/jackson/failing/DelegatingExternalProperty1003Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/creators/DelegatingExternalProperty1003Test.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.failing; +package com.fasterxml.jackson.databind.creators; import com.fasterxml.jackson.annotation.*; @@ -35,8 +35,10 @@ static class Delegate { public interface Hero { } static class Superman implements Hero { + String name = "superman"; + public String getName() { - return "superman"; + return name; } } @@ -46,11 +48,8 @@ public void testExtrnalPropertyDelegatingCreator() throws Exception final String json = mapper.writeValueAsString(new HeroBattle(new Superman())); -//System.err.println("JSON: "+json); final HeroBattle battle = mapper.readValue(json, HeroBattle.class); - assert battle.getHero() instanceof Superman; + assertTrue(battle.getHero() instanceof Superman); } - - } From 1ce65084c4c8a0e637fe83f1d08e4f64b03564bd Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 24 Nov 2015 21:45:26 -0800 Subject: [PATCH 027/124] Back port #1003 fix for 2.6.4 --- release-notes/VERSION | 2 ++ 1 file changed, 2 insertions(+) diff --git a/release-notes/VERSION b/release-notes/VERSION index cee298bdeb..8228bc8625 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -10,6 +10,8 @@ Project: jackson-databind (reported by Antibrumm@github) #989: Deserialization from "{}" to java.lang.Object causes "out of END_OBJECT token" error (reported by Ievgen P) +#1003: JsonTypeInfo.As.EXTERNAL_PROPERTY does not work with a Delegate + (reported by alexwen@github) #1005: Synthetic constructors confusing Jackson data binding (reported by Jayson M) #1013: `@JsonUnwrapped` is not treated as assuming `@JsonProperty("")` From c966a034dd29d103dcdb708179b55feb426ecd68 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 24 Nov 2015 21:46:12 -0800 Subject: [PATCH 028/124] ... --- .../jackson/databind/deser/BeanDeserializer.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java index 592d07f875..06a09473d8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java @@ -717,6 +717,16 @@ protected Object deserializeWithExternalTypeId(JsonParser p, DeserializationCont if (_propertyBasedCreator != null) { return deserializeUsingPropertyBasedWithExternalTypeId(p, ctxt); } + if (_delegateDeserializer != null) { + /* 24-Nov-2015, tatu: Use of delegating creator needs to have precedence, and basically + * external type id handling just has to be ignored, as they would relate to target + * type and not delegate type. Whether this works as expected is another story, but + * there's no other way to really mix these conflicting features. + */ + return _valueInstantiator.createUsingDelegate(ctxt, + _delegateDeserializer.deserialize(p, ctxt)); + } + return deserializeWithExternalTypeId(p, ctxt, _valueInstantiator.createUsingDefault(ctxt)); } From 1ed892ecabe31ff5ef11960dadc04b33388d43f7 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 24 Nov 2015 22:17:56 -0800 Subject: [PATCH 029/124] Minor improvement for schema generation for `AtomicReference` --- .../ser/std/AtomicReferenceSerializer.java | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/AtomicReferenceSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/AtomicReferenceSerializer.java index 117388bf91..fabc34ad13 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/AtomicReferenceSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/AtomicReferenceSerializer.java @@ -5,9 +5,7 @@ import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.annotation.JsonInclude; - import com.fasterxml.jackson.core.JsonGenerator; - import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.introspect.Annotated; @@ -237,18 +235,6 @@ public boolean isUnwrappingSerializer() { return (_unwrapper != null); } - @Override - public JsonNode getSchema(SerializerProvider provider, Type typeHint) { - return createSchemaNode("any", true); - } - - @Override - public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) - throws JsonMappingException - { - visitor.expectAnyFormat(typeHint); - } - /* /********************************************************** /* Serialization methods @@ -290,12 +276,32 @@ public void serializeWithType(AtomicReference ref, return; } - // Otherwise apply type-prefix/suffix, otherwise std serialize: + // Otherwise apply type-prefix/suffix, then std serialize: typeSer.writeTypePrefixForScalar(ref, g); serialize(ref, g, provider); typeSer.writeTypeSuffixForScalar(ref, g); } + /* + /********************************************************** + /* Introspection support + /********************************************************** + */ + + @Override + public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) + throws JsonMappingException + { + JsonSerializer ser = _valueSerializer; + if (ser == null) { + ser = _findSerializer(visitor.getProvider(), _referredType, _property); + if (_unwrapper != null) { + ser = ser.unwrappingSerializer(_unwrapper); + } + } + ser.acceptJsonFormatVisitor(visitor, _referredType); + } + /* /********************************************************** /* Helper methods From 9305cbb61f00c16c1fa8398d6af42dbbaca22d9a Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 24 Nov 2015 22:30:38 -0800 Subject: [PATCH 030/124] ... --- .../jackson/databind/util/StdDateFormat.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java b/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java index 9faf440d11..21ae2ebfb4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/StdDateFormat.java @@ -125,14 +125,6 @@ public StdDateFormat() { _locale = DEFAULT_LOCALE; } - /** - * @deprecated Since 2.4, use variant that also takes Locale - */ - @Deprecated // since 2.4 - public StdDateFormat(TimeZone tz) { - this(tz, DEFAULT_LOCALE); - } - public StdDateFormat(TimeZone tz, Locale loc) { _timezone = tz; _locale = loc; @@ -266,14 +258,12 @@ public Date parse(String dateStr, ParsePosition pos) if (looksLikeISO8601(dateStr)) { // also includes "plain" return parseAsISO8601(dateStr, pos); } - /* 14-Feb-2010, tatu: As per [JACKSON-236], better also - * consider "stringified" simple time stamp - */ + // Also consider "stringified" simple time stamp int i = dateStr.length(); while (--i >= 0) { char ch = dateStr.charAt(i); if (ch < '0' || ch > '9') { - // 07-Aug-2013, tatu: And #267 points out that negative numbers should also work + // 07-Aug-2013, tatu: And [databind#267] points out that negative numbers should also work if (i > 0 || ch != '-') { break; } From 040b519de409e2fbdc271721caa648931f213b50 Mon Sep 17 00:00:00 2001 From: Hugo Wood Date: Wed, 25 Nov 2015 21:41:03 +0100 Subject: [PATCH 031/124] Deduplicated delegate type computation code in CreatorCollector --- .../databind/deser/impl/CreatorCollector.java | 62 +++++++------------ 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java b/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java index 0ea13fdaf4..bfa1861c55 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/impl/CreatorCollector.java @@ -87,50 +87,13 @@ public CreatorCollector(BeanDescription beanDesc, MapperConfig config) public ValueInstantiator constructValueInstantiator(DeserializationConfig config) { - JavaType delegateType; - boolean maybeVanilla = !_hasNonDefaultCreator; - - if (maybeVanilla || (_creators[C_DELEGATE] == null)) { - delegateType = null; - } else { - // need to find type... - int ix = 0; - if (_delegateArgs != null) { - for (int i = 0, len = _delegateArgs.length; i < len; ++i) { - if (_delegateArgs[i] == null) { // marker for delegate itself - ix = i; - break; - } - } - } - delegateType = _creators[C_DELEGATE].getParameterType(ix); - } - - JavaType arrayDelegateType; - - if (maybeVanilla || (_creators[C_ARRAY_DELEGATE] == null)) { - arrayDelegateType = null; - } else { - // need to find type... - int ix = 0; - if (_arrayDelegateArgs != null) { - for (int i = 0, len = _arrayDelegateArgs.length; i < len; ++i) { - if (_arrayDelegateArgs[i] == null) { // marker for delegate itself - ix = i; - break; - } - } - } - arrayDelegateType = _creators[C_ARRAY_DELEGATE].getParameterType(ix); - } - + final JavaType delegateType = _computeDelegateType(_creators[C_DELEGATE], _delegateArgs); + final JavaType arrayDelegateType = _computeDelegateType(_creators[C_ARRAY_DELEGATE], _arrayDelegateArgs); final JavaType type = _beanDesc.getType(); // Any non-standard creator will prevent; with one exception: int-valued constructor // that standard containers have can be ignored - maybeVanilla &= !_hasNonDefaultCreator; - - if (maybeVanilla) { + if (!_hasNonDefaultCreator) { /* 10-May-2014, tatu: If we have nothing special, and we are dealing with one * of "well-known" types, can create a non-reflection-based instantiator. */ @@ -303,6 +266,25 @@ public boolean hasPropertyBasedCreator() { /********************************************************** */ + private JavaType _computeDelegateType(AnnotatedWithParams creator, SettableBeanProperty[] delegateArgs) + { + if (!_hasNonDefaultCreator || (creator == null)) { + return null; + } else { + // need to find type... + int ix = 0; + if (delegateArgs != null) { + for (int i = 0, len = delegateArgs.length; i < len; ++i) { + if (delegateArgs[i] == null) { // marker for delegate itself + ix = i; + break; + } + } + } + return creator.getParameterType(ix); + } + } + private T _fixAccess(T member) { if (member != null && _canFixAccess) { From 5fa925369412a00751aebe54419b729fd87d0c86 Mon Sep 17 00:00:00 2001 From: Hugo Wood Date: Wed, 25 Nov 2015 21:54:18 +0100 Subject: [PATCH 032/124] Deduplicated creation through delegate code in StdValueInstantiator --- .../deser/std/StdValueInstantiator.java | 90 +++++++++---------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java index 6968c5dea5..d072cb04b8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java @@ -252,60 +252,13 @@ public Object createFromObjectWith(DeserializationContext ctxt, Object[] args) t @Override public Object createUsingDelegate(DeserializationContext ctxt, Object delegate) throws IOException { - if (_delegateCreator == null) { // sanity-check; caller should check - throw new IllegalStateException("No delegate constructor for "+getValueTypeDesc()); - } - try { - // First simple case: just delegate, no injectables - if (_delegateArguments == null) { - return _delegateCreator.call1(delegate); - } - // And then the case with at least one injectable... - final int len = _delegateArguments.length; - Object[] args = new Object[len]; - for (int i = 0; i < len; ++i) { - SettableBeanProperty prop = _delegateArguments[i]; - if (prop == null) { // delegate - args[i] = delegate; - } else { // nope, injectable: - args[i] = ctxt.findInjectableValue(prop.getInjectableValueId(), prop, null); - } - } - // and then try calling with full set of arguments - return _delegateCreator.call(args); - } catch (Throwable t) { - throw rewrapCtorProblem(ctxt, t); - } + return createUsingDelegate(_delegateCreator, _delegateArguments, ctxt, delegate); } @Override public Object createUsingArrayDelegate(DeserializationContext ctxt, Object delegate) throws IOException { - if (_arrayDelegateCreator == null) { // sanity-check; caller should check - // try falling back to the classic delegate - return createUsingDelegate(ctxt, delegate); - } - try { - // First simple case: just delegate, no injectables - if (_arrayDelegateArguments == null) { - return _arrayDelegateCreator.call1(delegate); - } - // And then the case with at least one injectable... - final int len = _arrayDelegateArguments.length; - Object[] args = new Object[len]; - for (int i = 0; i < len; ++i) { - SettableBeanProperty prop = _arrayDelegateArguments[i]; - if (prop == null) { // delegate - args[i] = delegate; - } else { // nope, injectable: - args[i] = ctxt.findInjectableValue(prop.getInjectableValueId(), prop, null); - } - } - // and then try calling with full set of arguments - return _arrayDelegateCreator.call(args); - } catch (Throwable t) { - throw rewrapCtorProblem(ctxt, t); - } + return createUsingDelegate(_arrayDelegateCreator, _arrayDelegateArguments, ctxt, delegate); } /* @@ -491,4 +444,43 @@ protected JsonMappingException rewrapCtorProblem(DeserializationContext ctxt, } return wrapAsJsonMappingException(ctxt, t); } + + /* + /********************************************************** + /* Helper methods + /********************************************************** + */ + + private Object createUsingDelegate( + AnnotatedWithParams delegateCreator, + SettableBeanProperty[] delegateArguments, + DeserializationContext ctxt, + Object delegate) + throws IOException + { + if (delegateCreator == null) { // sanity-check; caller should check + throw new IllegalStateException("No delegate constructor for "+getValueTypeDesc()); + } + try { + // First simple case: just delegate, no injectables + if (delegateArguments == null) { + return delegateCreator.call1(delegate); + } + // And then the case with at least one injectable... + final int len = delegateArguments.length; + Object[] args = new Object[len]; + for (int i = 0; i < len; ++i) { + SettableBeanProperty prop = delegateArguments[i]; + if (prop == null) { // delegate + args[i] = delegate; + } else { // nope, injectable: + args[i] = ctxt.findInjectableValue(prop.getInjectableValueId(), prop, null); + } + } + // and then try calling with full set of arguments + return delegateCreator.call(args); + } catch (Throwable t) { + throw rewrapCtorProblem(ctxt, t); + } + } } From 9dbee041b69865e80549de51b679138b254e0571 Mon Sep 17 00:00:00 2001 From: Hugo Wood Date: Wed, 25 Nov 2015 21:59:00 +0100 Subject: [PATCH 033/124] createUsingArrayDelegate should fallback to the class createUsingDelegate if there's no array delegate set --- .../databind/deser/std/StdValueInstantiator.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java index d072cb04b8..96b30e50a9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/StdValueInstantiator.java @@ -252,13 +252,17 @@ public Object createFromObjectWith(DeserializationContext ctxt, Object[] args) t @Override public Object createUsingDelegate(DeserializationContext ctxt, Object delegate) throws IOException { - return createUsingDelegate(_delegateCreator, _delegateArguments, ctxt, delegate); + return _createUsingDelegate(_delegateCreator, _delegateArguments, ctxt, delegate); } @Override public Object createUsingArrayDelegate(DeserializationContext ctxt, Object delegate) throws IOException { - return createUsingDelegate(_arrayDelegateCreator, _arrayDelegateArguments, ctxt, delegate); + if (_arrayDelegateCreator == null) { // sanity-check; caller should check + // fallback to the classic delegate creator + return createUsingDelegate(ctxt, delegate); + } + return _createUsingDelegate(_arrayDelegateCreator, _arrayDelegateArguments, ctxt, delegate); } /* @@ -451,7 +455,7 @@ protected JsonMappingException rewrapCtorProblem(DeserializationContext ctxt, /********************************************************** */ - private Object createUsingDelegate( + private Object _createUsingDelegate( AnnotatedWithParams delegateCreator, SettableBeanProperty[] delegateArguments, DeserializationContext ctxt, From 8ee51ad3a84987359401719198d7c0bff597b1a3 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Thu, 26 Nov 2015 13:31:17 -0800 Subject: [PATCH 034/124] Prepare for 2.7.0-rc1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 195e0cc9c4..9191240234 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.fasterxml.jackson.core jackson-databind - 2.7.0-SNAPSHOT + 2.7.0-rc1-SNAPSHOT jackson-databind bundle General data-binding functionality for Jackson: works on core streaming API @@ -52,7 +52,7 @@ com.fasterxml.jackson.core jackson-core - 2.7.0-SNAPSHOT + 2.7.0-rc1 com.fasterxml.jackson.core From 6de5dc849202d964f6c797f33f625da8df9979cf Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Thu, 26 Nov 2015 20:35:50 -0800 Subject: [PATCH 038/124] Resolve a minor NPE problem for virtual properties --- .../jackson/databind/ser/BeanPropertyWriter.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java b/src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java index 0b39b61c57..d0068a46a8 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java @@ -586,7 +586,13 @@ public JavaType getFullPropertyType() { */ @Deprecated public Class getPropertyType() { - return (_accessorMethod != null) ? _accessorMethod.getReturnType() : _field.getType(); + if (_accessorMethod != null) { + return _accessorMethod.getReturnType(); + } + if (_field != null) { + return _field.getType(); + } + return null; } /** From bf2b2b0c89dc2e16b057c04ab127bdacc981d67f Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 27 Nov 2015 17:50:46 -0800 Subject: [PATCH 039/124] Add a new test for serialiation type override --- .../JacksonAnnotationIntrospector.java | 4 +-- .../databind/ser/TestJsonSerializeAs.java | 36 +++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index c9116330b3..6cdea5b29f 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -131,8 +131,8 @@ public String findEnumValue(Enum value) return value.name(); } - @Override - public String[] findEnumValues(Class enumType, Enum[] enumValues, String[] names) { + @Override // since 2.7 + public String[] findEnumValues(Class enumType, Enum[] enumValues, String[] names) { HashMap expl = null; for (Field f : ClassUtil.getDeclaredFields(enumType)) { if (!f.isEnumConstant()) { diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerializeAs.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerializeAs.java index d08aa7a119..e724d96518 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerializeAs.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerializeAs.java @@ -7,7 +7,6 @@ public class TestJsonSerializeAs extends BaseMapTest { - // [JACKSON-799] stuff: public interface Fooable { public int getFoo(); } @@ -20,6 +19,12 @@ public static class FooImpl implements Fooable { public int getBar() { return 15; } } + static class FooImplNoAnno implements Fooable { + @Override + public int getFoo() { return 42; } + public int getBar() { return 15; } + } + public class Fooables { public FooImpl[] getFoos() { return new FooImpl[] { new FooImpl() }; @@ -32,6 +37,14 @@ public FooImpl getFoo() { } } + // Also test via Field + static class FooableWithFieldWrapper { + @JsonSerialize(as=Fooable.class) + public FooImplNoAnno getFoo() { + return new FooImplNoAnno(); + } + } + /* /********************************************************** /* Test methods @@ -40,19 +53,22 @@ public FooImpl getFoo() { private final ObjectWriter WRITER = objectWriter(); - // [JACKSON-799] - public void testSerializeAsInClass() throws IOException - { + public void testSerializeAsInClass() throws IOException { assertEquals("{\"foo\":42}", WRITER.writeValueAsString(new FooImpl())); } - public void testSerializeAsForArrayProp() throws IOException - { - assertEquals("{\"foos\":[{\"foo\":42}]}", WRITER.writeValueAsString(new Fooables())); + public void testSerializeAsForArrayProp() throws IOException { + assertEquals("{\"foos\":[{\"foo\":42}]}", + WRITER.writeValueAsString(new Fooables())); + } + + public void testSerializeAsForSimpleProp() throws IOException { + assertEquals("{\"foo\":{\"foo\":42}}", + WRITER.writeValueAsString(new FooableWrapper())); } - public void testSerializeAsForSimpleProp() throws IOException - { - assertEquals("{\"foo\":{\"foo\":42}}", WRITER.writeValueAsString(new FooableWrapper())); + public void testSerializeWithFieldAnno() throws IOException { + assertEquals("{\"foo\":{\"foo\":42}}", + WRITER.writeValueAsString(new FooableWithFieldWrapper())); } } From e59a1ae717f7289c9955faece8cf672ed12c3fd6 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 27 Nov 2015 18:21:11 -0800 Subject: [PATCH 040/124] add a unit test for serialization type overrides --- .../databind/ser/TestJsonSerializeAs.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerializeAs.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerializeAs.java index d08aa7a119..a7fe0c2523 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerializeAs.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestJsonSerializeAs.java @@ -7,7 +7,6 @@ public class TestJsonSerializeAs extends BaseMapTest { - // [JACKSON-799] stuff: public interface Fooable { public int getFoo(); } @@ -20,6 +19,12 @@ public static class FooImpl implements Fooable { public int getBar() { return 15; } } + static class FooImplNoAnno implements Fooable { + @Override + public int getFoo() { return 42; } + public int getBar() { return 15; } + } + public class Fooables { public FooImpl[] getFoos() { return new FooImpl[] { new FooImpl() }; @@ -32,6 +37,14 @@ public FooImpl getFoo() { } } + // Also test via Field + static class FooableWithFieldWrapper { + @JsonSerialize(as=Fooable.class) + public Fooable getFoo() { + return new FooImplNoAnno(); + } + } + /* /********************************************************** /* Test methods @@ -40,19 +53,22 @@ public FooImpl getFoo() { private final ObjectWriter WRITER = objectWriter(); - // [JACKSON-799] - public void testSerializeAsInClass() throws IOException - { + public void testSerializeAsInClass() throws IOException { assertEquals("{\"foo\":42}", WRITER.writeValueAsString(new FooImpl())); } - public void testSerializeAsForArrayProp() throws IOException - { - assertEquals("{\"foos\":[{\"foo\":42}]}", WRITER.writeValueAsString(new Fooables())); + public void testSerializeAsForArrayProp() throws IOException { + assertEquals("{\"foos\":[{\"foo\":42}]}", + WRITER.writeValueAsString(new Fooables())); + } + + public void testSerializeAsForSimpleProp() throws IOException { + assertEquals("{\"foo\":{\"foo\":42}}", + WRITER.writeValueAsString(new FooableWrapper())); } - public void testSerializeAsForSimpleProp() throws IOException - { - assertEquals("{\"foo\":{\"foo\":42}}", WRITER.writeValueAsString(new FooableWrapper())); + public void testSerializeWithFieldAnno() throws IOException { + assertEquals("{\"foo\":{\"foo\":42}}", + WRITER.writeValueAsString(new FooableWithFieldWrapper())); } } From 3c35f1c7b56ee6f599d153b300de36faeab60c52 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 28 Nov 2015 12:18:48 -0800 Subject: [PATCH 041/124] Fix #1025: AnnotationIntrospectorPair.findPropertyInclusion (added unit test too) --- pom.xml | 15 +++-- .../jackson/databind/JsonSerializer.java | 2 +- .../AnnotationIntrospectorPair.java | 5 +- .../ser/std/AtomicReferenceSerializer.java | 1 - .../databind/ser/std/StdSerializer.java | 10 +-- .../introspect/IntrospectorPairTest.java | 64 +++++++++++++++++++ 6 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/introspect/IntrospectorPairTest.java diff --git a/pom.xml b/pom.xml index e4a9cddf8e..31d6566e58 100644 --- a/pom.xml +++ b/pom.xml @@ -47,30 +47,31 @@ com.fasterxml.jackson.core jackson-annotations - + + 2.7.0-rc2-SNAPSHOT com.fasterxml.jackson.core jackson-core - 2.7.0-rc1 + 2.7.0-rc2-SNAPSHOT - - + + org.powermock powermock-module-junit4 1.6.3 test - - + + org.powermock powermock-api-mockito 1.6.3 test - + javax.measure diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonSerializer.java b/src/main/java/com/fasterxml/jackson/databind/JsonSerializer.java index bc06d49928..88e5f50328 100644 --- a/src/main/java/com/fasterxml/jackson/databind/JsonSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/JsonSerializer.java @@ -190,7 +190,7 @@ public void serializeWithType(T value, JsonGenerator gen, SerializerProvider ser * @since 2.0 * * @deprecated Since 2.5 Use {@link #isEmpty(SerializerProvider, Object)} instead; - * will be removed from 2.7 + * will be removed from 2.8 */ @Deprecated public boolean isEmpty(T value) { diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java index af38f4403d..e4d42c0dec 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java @@ -343,9 +343,10 @@ public JsonInclude.Include findSerializationInclusionForContent(Annotated a, Jso } @Override - public JsonInclude.Value findPropertyInclusion(Annotated a) { + public JsonInclude.Value findPropertyInclusion(Annotated a) + { JsonInclude.Value v2 = _secondary.findPropertyInclusion(a); - JsonInclude.Value v1 = _secondary.findPropertyInclusion(a); + JsonInclude.Value v1 = _primary.findPropertyInclusion(a); if (v2 == null) { // shouldn't occur but return v1; diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/AtomicReferenceSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/AtomicReferenceSerializer.java index fabc34ad13..ca8c191d44 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/AtomicReferenceSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/AtomicReferenceSerializer.java @@ -1,7 +1,6 @@ package com.fasterxml.jackson.databind.ser.std; import java.io.IOException; -import java.lang.reflect.Type; import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdSerializer.java index 4107538a0b..3b0509fdd6 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdSerializer.java @@ -53,7 +53,7 @@ public abstract class StdSerializer /* Life-cycle /********************************************************** */ - + protected StdSerializer(Class t) { _handledType = t; } @@ -62,7 +62,7 @@ protected StdSerializer(Class t) { protected StdSerializer(JavaType type) { _handledType = (Class) type.getRawClass(); } - + /** * Alternate constructor that is (alas!) needed to work * around kinks of generic type handling @@ -79,13 +79,13 @@ protected StdSerializer(Class t, boolean dummy) { protected StdSerializer(StdSerializer src) { _handledType = (Class) src._handledType; } - + /* /********************************************************** /* Accessors /********************************************************** */ - + @Override public Class handledType() { return _handledType; } @@ -94,7 +94,7 @@ protected StdSerializer(StdSerializer src) { /* Serialization /********************************************************** */ - + @Override public abstract void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException; diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/IntrospectorPairTest.java b/src/test/java/com/fasterxml/jackson/databind/introspect/IntrospectorPairTest.java new file mode 100644 index 0000000000..73a3a423b9 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/IntrospectorPairTest.java @@ -0,0 +1,64 @@ +package com.fasterxml.jackson.databind.introspect; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.*; + +// started with [databind#1025] in mind +@SuppressWarnings("serial") +public class IntrospectorPairTest extends BaseMapTest +{ + static class Introspector1 extends AnnotationIntrospector { + @Override + public Version version() { + return Version.unknownVersion(); + } + + @Override + public JsonInclude.Value findPropertyInclusion(Annotated a) { + return JsonInclude.Value.empty() + .withContentInclusion(JsonInclude.Include.ALWAYS) + .withValueInclusion(JsonInclude.Include.NON_ABSENT); + } + } + + static class Introspector2 extends AnnotationIntrospector { + @Override + public Version version() { + return Version.unknownVersion(); + } + + @Override + public JsonInclude.Value findPropertyInclusion(Annotated a) { + return JsonInclude.Value.empty() + .withContentInclusion(JsonInclude.Include.NON_EMPTY) + .withValueInclusion(JsonInclude.Include.USE_DEFAULTS); + } + } + + /* + /********************************************************** + /* Test methods + /********************************************************** + */ + + private final AnnotationIntrospectorPair introPair12 + = new AnnotationIntrospectorPair(new Introspector1(), new Introspector2()); + + private final AnnotationIntrospectorPair introPair21 + = new AnnotationIntrospectorPair(new Introspector2(), new Introspector1()); + + // for [databind#1025] + public void testInclusionMerging() throws Exception + { + // argument is ignored by test introspectors, may be null + JsonInclude.Value v12 = introPair12.findPropertyInclusion(null); + JsonInclude.Value v21 = introPair21.findPropertyInclusion(null); + + assertEquals(JsonInclude.Include.ALWAYS, v12.getContentInclusion()); + assertEquals(JsonInclude.Include.NON_ABSENT, v12.getValueInclusion()); + + assertEquals(JsonInclude.Include.NON_EMPTY, v21.getContentInclusion()); + assertEquals(JsonInclude.Include.NON_ABSENT, v21.getValueInclusion()); + } +} From 7e5f014755e3e98ce4e11b8a89d800e9ad86eb60 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 28 Nov 2015 12:27:59 -0800 Subject: [PATCH 042/124] Minor improvement to handling of JsonFormat.Value with AnnotationIntrospectorPair, should try merging --- .../introspect/AnnotationIntrospectorPair.java | 10 +++++++--- .../jackson/databind/type/PolymorphicList036Test.java | 5 +++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java index e4d42c0dec..6f83e0d267 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java @@ -404,11 +404,15 @@ public ObjectIdInfo findObjectReferenceInfo(Annotated ann, ObjectIdInfo objectId objectIdInfo = _primary.findObjectReferenceInfo(ann, objectIdInfo); return objectIdInfo; } - + @Override public JsonFormat.Value findFormat(Annotated ann) { - JsonFormat.Value r = _primary.findFormat(ann); - return (r == null) ? _secondary.findFormat(ann) : r; + JsonFormat.Value v1 = _primary.findFormat(ann); + JsonFormat.Value v2 = _secondary.findFormat(ann); + if (v2 == null) { // shouldn't occur but just in case + return v1; + } + return v2.withOverrides(v1); } @Override diff --git a/src/test/java/com/fasterxml/jackson/databind/type/PolymorphicList036Test.java b/src/test/java/com/fasterxml/jackson/databind/type/PolymorphicList036Test.java index 55506a3c3a..168b63608f 100644 --- a/src/test/java/com/fasterxml/jackson/databind/type/PolymorphicList036Test.java +++ b/src/test/java/com/fasterxml/jackson/databind/type/PolymorphicList036Test.java @@ -99,10 +99,11 @@ public void testPolymorphicWithOverride() throws Exception list.add("value 2"); String serialized = MAPPER.writeValueAsString(list); - System.out.println(serialized); +// System.out.println(serialized); StringyList deserialized = MAPPER.readValue(serialized, type); - System.out.println(deserialized); +// System.out.println(deserialized); + assertNotNull(deserialized); } } From 91e41a94f3b5d0dad48ce47ccd6c4f3272a2385f Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 29 Nov 2015 17:19:39 -0800 Subject: [PATCH 043/124] Add a unit test trying to reproduce #1026 --- .../jackson/databind/introspect/TestNamingStrategyStd.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java index a332e027b5..5dfb491963 100644 --- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TestNamingStrategyStd.java @@ -165,7 +165,10 @@ static class DefaultNaming { {"_UserName", "user_name"}, {"_User_Name", "user_name"}, {"UGLY_NAME", "ugly_name"}, - {"_Bars", "bars" } + {"_Bars", "bars" }, + // [databind#1026] + {"usId", "us_id" }, + {"uId", "u_id" }, }); private ObjectMapper _lcWithUndescoreMapper; From 4ebb58bc7bcad564183f03f1d6626f5bc75014a9 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 29 Nov 2015 17:31:18 -0800 Subject: [PATCH 044/124] Fix #1027 --- .../databind/AbstractTypeResolver.java | 25 +++++++++++++++---- .../deser/BeanDeserializerFactory.java | 8 +++--- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/AbstractTypeResolver.java b/src/main/java/com/fasterxml/jackson/databind/AbstractTypeResolver.java index e60c4743c8..da6c889fc5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AbstractTypeResolver.java +++ b/src/main/java/com/fasterxml/jackson/databind/AbstractTypeResolver.java @@ -29,24 +29,39 @@ public abstract class AbstractTypeResolver public JavaType findTypeMapping(DeserializationConfig config, JavaType type) { return null; } - + + // !!! 29-Nov-2015, tatu: TODO: mark deprecated in 2.8 + /** + * Older variant of {@link #resolveAbstractType(DeserializationConfig, BeanDescription)}; + * obsoleted in 2.7, to be deprecated in 2.8 + */ + public JavaType resolveAbstractType(DeserializationConfig config, + JavaType type) { + return null; + } + /** * Method called to try to resolve an abstract type into * concrete type (usually for purposes of deserializing), * when no concrete implementation was found. * It will be called after checking all other possibilities, * including defaulting. + *

    + * Default implementation will call obsolete method for Jackson 2.7, + * to try to keep some level of backwards compatibility. * * @param config Configuration in use; should always be of type * DeserializationConfig - * @param type Type for which materialization maybe needed + * @param typeDesc Description of the POJO type to resolve * * @return Resolved concrete type (which should retain generic * type parameters of input type, if any), if resolution succeeds; - * null if resolver does not know how to resolve type + * null if resolver does not know how to resolve given type + * + * @since 2.7 */ public JavaType resolveAbstractType(DeserializationConfig config, - JavaType type) { - return null; + BeanDescription typeDesc) { + return resolveAbstractType(config, typeDesc.getType()); } } diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 8d30fb5e4f..7cb79e192b 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -179,18 +179,16 @@ protected JavaType materializeAbstractType(DeserializationContext ctxt, JavaType type, BeanDescription beanDesc) throws JsonMappingException { - final JavaType abstractType = beanDesc.getType(); - // [JACKSON-502]: Now it is possible to have multiple resolvers too, - // as they are registered via module interface. + // May have multiple resolvers, call in precedence order until one returns non-null for (AbstractTypeResolver r : _factoryConfig.abstractTypeResolvers()) { - JavaType concrete = r.resolveAbstractType(ctxt.getConfig(), abstractType); + JavaType concrete = r.resolveAbstractType(ctxt.getConfig(), beanDesc); if (concrete != null) { return concrete; } } return null; } - + /* /********************************************************** /* Public construction method beyond DeserializerFactory API: From 063bacab2ba882fca0fe4241a5e6fb48bd6ffb43 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 29 Nov 2015 17:52:53 -0800 Subject: [PATCH 045/124] Support AnnotatedClass.getType(); actually useful unlike my earlier thoughts --- .../fasterxml/jackson/databind/introspect/AnnotatedClass.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java index b1af44a28c..b91ccd6154 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java @@ -283,9 +283,7 @@ protected AnnotationMap getAllAnnotations() { @Override public JavaType getType() { - // 16-Oct-2015, tatu: Does this make any sense? Technically doable but -// return _type; - throw new UnsupportedOperationException("Should not be called on AnnotatedClass"); + return _type; } /* From 3c17052a38519be00589a8f7243e44506c20240e Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 29 Nov 2015 18:08:49 -0800 Subject: [PATCH 046/124] Minor improvement: do not try to materialize primitive types, even though they are marked as abstract --- .../jackson/databind/deser/BeanDeserializerFactory.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java index 7cb79e192b..43b748003c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java @@ -114,7 +114,9 @@ public JsonDeserializer createBeanDeserializer(DeserializationContext ct /* Or, for abstract types, may have alternate means for resolution * (defaulting, materialization) */ - if (type.isAbstract()) { + // 29-Nov-2015, tatu: Also, filter out calls to primitive types, they are + // not something we could materialize anything for + if (type.isAbstract() && !type.isPrimitive()) { // Let's make it possible to materialize abstract types. JavaType concreteType = materializeAbstractType(ctxt, type, beanDesc); if (concreteType != null) { From 6fe1ff0dc1d51f236d666bd4c9d5d139449c2841 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 30 Nov 2015 16:03:58 -0800 Subject: [PATCH 047/124] Fix #1023 --- .../databind/AnnotationIntrospector.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index 9b46fc54b5..e57c8194eb 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -789,16 +789,22 @@ public JavaType refineSerializationType(final MapperConfig config, // Ok: start by refining the main type itself; common to all types Class serClass = findSerializationType(a); - if ((serClass != null) && !type.hasRawClass(serClass)) { - try { - // 11-Oct-2015, tatu: For deser, we call `TypeFactory.constructSpecializedType()`, - // may be needed here too in future? - type = tf.constructGeneralizedType(type, serClass); - } catch (IllegalArgumentException iae) { - throw new JsonMappingException(null, - String.format("Failed to widen type %s with annotation (value %s), from '%s': %s", - type, serClass.getName(), a.getName(), iae.getMessage()), - iae); + if (serClass != null) { + if (type.hasRawClass(serClass)) { + // 30-Nov-2015, tatu: As per [databind#1023], need to allow forcing of + // static typing this way + type = type.withStaticTyping(); + } else { + try { + // 11-Oct-2015, tatu: For deser, we call `TypeFactory.constructSpecializedType()`, + // may be needed here too in future? + type = tf.constructGeneralizedType(type, serClass); + } catch (IllegalArgumentException iae) { + throw new JsonMappingException(null, + String.format("Failed to widen type %s with annotation (value %s), from '%s': %s", + type, serClass.getName(), a.getName(), iae.getMessage()), + iae); + } } } // Then further processing for container types From f19875e3045c39bde8b0cb4673004115de6c9eae Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 30 Nov 2015 16:17:48 -0800 Subject: [PATCH 048/124] test refactoring --- .../databind/AnnotationIntrospector.java | 42 +++++++++++-------- .../filter/MapInclusionTest.java} | 4 +- 2 files changed, 27 insertions(+), 19 deletions(-) rename src/test/java/com/fasterxml/jackson/{failing/TestEmptyMapSerialization588.java => databind/filter/MapInclusionTest.java} (91%) diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index e57c8194eb..e96c77ffea 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -814,32 +814,40 @@ public JavaType refineSerializationType(final MapperConfig config, JavaType keyType = type.getKeyType(); Class keyClass = findSerializationKeyType(a, keyType); if (keyClass != null) { - try { - keyType = tf.constructGeneralizedType(keyType, keyClass); - type = ((MapLikeType) type).withKeyType(keyType); - } catch (IllegalArgumentException iae) { - throw new JsonMappingException(null, - String.format("Failed to widen key type of %s with concrete-type annotation (value %s), from '%s': %s", - type, keyClass.getName(), a.getName(), iae.getMessage()), - iae); + if (keyType.hasRawClass(keyClass)) { + keyType = keyType.withStaticTyping(); + } else { + try { + keyType = tf.constructGeneralizedType(keyType, keyClass); + } catch (IllegalArgumentException iae) { + throw new JsonMappingException(null, + String.format("Failed to widen key type of %s with concrete-type annotation (value %s), from '%s': %s", + type, keyClass.getName(), a.getName(), iae.getMessage()), + iae); + } } + type = ((MapLikeType) type).withKeyType(keyType); } } JavaType contentType = type.getContentType(); if (contentType != null) { // collection[like], map[like], array, reference // And then value types for all containers: - Class contentClass = findSerializationContentType(a, type.getContentType()); + Class contentClass = findSerializationContentType(a, contentType); if (contentClass != null) { - try { - contentType = tf.constructGeneralizedType(contentType, contentClass); - type = type.withContentType(contentType); - } catch (IllegalArgumentException iae) { - throw new JsonMappingException(null, - String.format("Failed to widen value type of %s with concrete-type annotation (value %s), from '%s': %s", - type, contentClass.getName(), a.getName(), iae.getMessage()), - iae); + if (contentType.hasRawClass(contentClass)) { + contentType = contentType.withStaticTyping(); + } else { + try { + contentType = tf.constructGeneralizedType(contentType, contentClass); + } catch (IllegalArgumentException iae) { + throw new JsonMappingException(null, + String.format("Failed to widen value type of %s with concrete-type annotation (value %s), from '%s': %s", + type, contentClass.getName(), a.getName(), iae.getMessage()), + iae); + } } + type = type.withContentType(contentType); } } return type; diff --git a/src/test/java/com/fasterxml/jackson/failing/TestEmptyMapSerialization588.java b/src/test/java/com/fasterxml/jackson/databind/filter/MapInclusionTest.java similarity index 91% rename from src/test/java/com/fasterxml/jackson/failing/TestEmptyMapSerialization588.java rename to src/test/java/com/fasterxml/jackson/databind/filter/MapInclusionTest.java index 9859bc352d..a5ccc57020 100644 --- a/src/test/java/com/fasterxml/jackson/failing/TestEmptyMapSerialization588.java +++ b/src/test/java/com/fasterxml/jackson/databind/filter/MapInclusionTest.java @@ -1,4 +1,4 @@ -package com.fasterxml.jackson.failing; +package com.fasterxml.jackson.databind.filter; import java.io.IOException; import java.util.LinkedHashMap; @@ -7,7 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.*; -public class TestEmptyMapSerialization588 extends BaseMapTest +public class MapInclusionTest extends BaseMapTest { static class NoEmptiesMapContainer { @JsonInclude(value=JsonInclude.Include.NON_EMPTY, From 53fb51fffd1a68b2203f401454f143b62935fc84 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 30 Nov 2015 16:28:16 -0800 Subject: [PATCH 049/124] Add a unit test for #921 --- .../BuilderDeserializationTest921.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/test/java/com/fasterxml/jackson/failing/BuilderDeserializationTest921.java diff --git a/src/test/java/com/fasterxml/jackson/failing/BuilderDeserializationTest921.java b/src/test/java/com/fasterxml/jackson/failing/BuilderDeserializationTest921.java new file mode 100644 index 0000000000..79beaee6f5 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/failing/BuilderDeserializationTest921.java @@ -0,0 +1,102 @@ +package com.fasterxml.jackson.failing; + +import java.util.List; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +public class BuilderDeserializationTest921 + extends BaseMapTest +{ + public static class MyPOJO { + public String x; + public String y; + + @JsonCreator + public MyPOJO(@JsonProperty("x") String x, @JsonProperty("y") String y) { + this.x = x; + this.y = y; + } + } + + @JsonDeserialize(builder = MyGenericPOJO.Builder.class) + public static class MyGenericPOJO { + private List data; + + private MyGenericPOJO(List d) { + data = d; + } + + public List getData() { + return data; + } + + public static class Builder { + private List data; + + public Builder withData(List d) { + data = d; + return this; + } + + public MyGenericPOJO build() { + return new MyGenericPOJO(data); + } + } + } + + public static class MyGenericPOJOWithCreator { + private List data; + + private MyGenericPOJOWithCreator(List d) { + data = d; + } + + @JsonCreator + public static MyGenericPOJOWithCreator create(@JsonProperty("data") List data) { + return new MyGenericPOJOWithCreator.Builder().withData(data).build(); + } + + public List getData() { + return data; + } + + public static class Builder { + private List data; + + public Builder withData(List d) { + data = d; + return this; + } + + public MyGenericPOJOWithCreator build() { + return new MyGenericPOJOWithCreator(data); + } + } + } + + public void testWithBuilder() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + final String json = aposToQuotes("{ 'data': [ { 'x': 'x', 'y': 'y' } ] }"); + final MyGenericPOJO deserialized = + mapper.readValue(json, new TypeReference>() {}); + assertEquals(1, deserialized.data.size()); + Object ob = deserialized.data.get(0); + assertNotNull(ob); + assertEquals(MyPOJO.class, ob.getClass()); + } + + public void testWithCreator() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + final String json = aposToQuotes("{ 'data': [ { 'x': 'x', 'y': 'y' } ] }"); + final MyGenericPOJOWithCreator deserialized = + mapper.readValue(json, + new TypeReference>() {}); + assertEquals(1, deserialized.data.size()); + Object ob = deserialized.data.get(0); + assertNotNull(ob); + assertEquals(MyPOJO.class, ob.getClass()); + } + } From 19e6d29bee1fc17472852773bf69ea86d5cd378f Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 1 Dec 2015 21:39:40 -0800 Subject: [PATCH 050/124] Fix #1032 --- backup/TypeSerializerWrapper.java | 142 ------- .../databind/deser/AbstractDeserializer.java | 53 ++- .../databind/deser/BeanDeserializerBase.java | 59 +-- .../databind/ser/std/EnumMapSerializer.java | 378 ------------------ .../deprecated/MiscDeprecatedTest.java | 28 -- .../jackson/databind/node/TestObjectNode.java | 8 +- 6 files changed, 66 insertions(+), 602 deletions(-) delete mode 100644 backup/TypeSerializerWrapper.java delete mode 100644 src/main/java/com/fasterxml/jackson/databind/ser/std/EnumMapSerializer.java delete mode 100644 src/test/java/com/fasterxml/jackson/databind/deprecated/MiscDeprecatedTest.java diff --git a/backup/TypeSerializerWrapper.java b/backup/TypeSerializerWrapper.java deleted file mode 100644 index b0ffb29ad8..0000000000 --- a/backup/TypeSerializerWrapper.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.fasterxml.jackson.databind.jsontype; - -import java.io.IOException; - -import com.fasterxml.jackson.annotation.JsonTypeInfo.As; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.BeanProperty; - -/** - * Helper class used in cases where we caller has to override source - * for type identifier, for example when serializing a value using - * a delegate or surrogate value, in which case type id is to be based - * on the original value, but serialization done using surrogate. - * - * @since 2.2 - */ -public class TypeSerializerWrapper - extends TypeSerializer -{ - /** - * Actual TypeSerializer to use - */ - protected final TypeSerializer _delegate; - - protected final Object _value; - - public TypeSerializerWrapper(TypeSerializer delegate, Object value) - { - _delegate = delegate; - _value = value; - } - - /* - /********************************************************** - /* TypeSerializer implementation, metadata - /********************************************************** - */ - - @Override - public TypeSerializer forProperty(BeanProperty prop) { - TypeSerializer d2 = _delegate.forProperty(prop); - if (d2 == _delegate) { - return this; - } - return new TypeSerializerWrapper(d2, _value); - } - - @Override - public As getTypeInclusion() { - return _delegate.getTypeInclusion(); - } - - @Override - public String getPropertyName() { - return _delegate.getPropertyName(); - } - - @Override - public TypeIdResolver getTypeIdResolver() { - return _delegate.getTypeIdResolver(); - } - - /* - /********************************************************** - /* TypeSerializer implementation, actual write methods - /********************************************************** - */ - - @Override - public void writeTypePrefixForScalar(Object value, JsonGenerator jgen) - throws IOException, JsonProcessingException { - _delegate.writeTypePrefixForScalar(_value, jgen); - } - - @Override - public void writeTypePrefixForObject(Object value, JsonGenerator jgen) - throws IOException, JsonProcessingException { - _delegate.writeTypePrefixForObject(_value, jgen); - } - - @Override - public void writeTypePrefixForArray(Object value, JsonGenerator jgen) - throws IOException, JsonProcessingException { - _delegate.writeTypePrefixForArray(_value, jgen); - } - - @Override - public void writeTypeSuffixForScalar(Object value, JsonGenerator jgen) - throws IOException, JsonProcessingException { - _delegate.writeTypeSuffixForScalar(_value, jgen); - } - - @Override - public void writeTypeSuffixForObject(Object value, JsonGenerator jgen) - throws IOException, JsonProcessingException { - _delegate.writeTypeSuffixForObject(_value, jgen); - } - - @Override - public void writeTypeSuffixForArray(Object value, JsonGenerator jgen) - throws IOException, JsonProcessingException { - _delegate.writeTypeSuffixForArray(_value, jgen); - } - - @Override - public void writeCustomTypePrefixForScalar(Object value, - JsonGenerator jgen, String typeId) throws IOException, JsonProcessingException { - _delegate.writeCustomTypePrefixForScalar(_value, jgen, typeId); - } - - @Override - public void writeCustomTypePrefixForObject(Object value, - JsonGenerator jgen, String typeId) throws IOException, JsonProcessingException { - _delegate.writeCustomTypePrefixForObject(_value, jgen, typeId); - } - - @Override - public void writeCustomTypePrefixForArray(Object value, JsonGenerator jgen, - String typeId) throws IOException, JsonProcessingException { - _delegate.writeCustomTypePrefixForArray(_value, jgen, typeId); - } - - @Override - public void writeCustomTypeSuffixForScalar(Object value, - JsonGenerator jgen, String typeId) throws IOException, JsonProcessingException { - _delegate.writeCustomTypeSuffixForScalar(_value, jgen, typeId); - } - - @Override - public void writeCustomTypeSuffixForObject(Object value, - JsonGenerator jgen, String typeId) throws IOException, - JsonProcessingException { - _delegate.writeCustomTypeSuffixForObject(_value, jgen, typeId); - } - - @Override - public void writeCustomTypeSuffixForArray(Object value, JsonGenerator jgen, - String typeId) throws IOException, JsonProcessingException { - _delegate.writeCustomTypeSuffixForArray(_value, jgen, typeId); - } -} diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java index bf0b8a40bb..7b13fbaaf4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/AbstractDeserializer.java @@ -109,31 +109,42 @@ public SettableBeanProperty findBackReference(String logicalName) { */ @Override - public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, + public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) - throws IOException, JsonProcessingException + throws IOException { // Hmmh. One tricky question; for scalar, is it an Object Id, or "Natural" type? // for now, prefer Object Id: if (_objectIdReader != null) { - JsonToken t = jp.getCurrentToken(); - // should be good enough check; we only care about Strings, integral numbers: - if (t != null && t.isScalarValue()) { - return _deserializeFromObjectId(jp, ctxt); + JsonToken t = p.getCurrentToken(); + if (t != null) { + // Most commonly, a scalar (int id, uuid String, ...) + if (t.isScalarValue()) { + return _deserializeFromObjectId(p, ctxt); + } + // but, with 2.5+, a simple Object-wrapped value also legal: + if (t == JsonToken.START_OBJECT) { + t = p.nextToken(); + } + if ((t == JsonToken.FIELD_NAME) && _objectIdReader.maySerializeAsObject() + && _objectIdReader.isValidReferencePropertyName(p.getCurrentName(), p)) { + return _deserializeFromObjectId(p, ctxt); + } + } } // First: support "natural" values (which are always serialized without type info!) - Object result = _deserializeIfNatural(jp, ctxt); + Object result = _deserializeIfNatural(p, ctxt); if (result != null) { return result; } - return typeDeserializer.deserializeTypedFromObject(jp, ctxt); + return typeDeserializer.deserializeTypedFromObject(p, ctxt); } @Override - public Object deserialize(JsonParser jp, DeserializationContext ctxt) - throws IOException, JsonProcessingException + public Object deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException { // This method should never be called... throw ctxt.instantiationException(_baseType.getRawClass(), @@ -146,28 +157,28 @@ public Object deserialize(JsonParser jp, DeserializationContext ctxt) /********************************************************** */ - protected Object _deserializeIfNatural(JsonParser jp, DeserializationContext ctxt) throws IOException + protected Object _deserializeIfNatural(JsonParser p, DeserializationContext ctxt) throws IOException { - /* As per [JACKSON-417], there is a chance we might be "natural" types + /* There is a chance we might be "natural" types * (String, Boolean, Integer, Double), which do not include any type information... * Care must be taken to only return this if return type matches, however. * Finally, we may have to consider possibility of custom handlers for * these values: but for now this should work ok. */ - switch (jp.getCurrentTokenId()) { + switch (p.getCurrentTokenId()) { case JsonTokenId.ID_STRING: if (_acceptString) { - return jp.getText(); + return p.getText(); } break; case JsonTokenId.ID_NUMBER_INT: if (_acceptInt) { - return jp.getIntValue(); + return p.getIntValue(); } break; case JsonTokenId.ID_NUMBER_FLOAT: if (_acceptDouble) { - return Double.valueOf(jp.getDoubleValue()); + return Double.valueOf(p.getDoubleValue()); } break; case JsonTokenId.ID_TRUE: @@ -188,18 +199,16 @@ protected Object _deserializeIfNatural(JsonParser jp, DeserializationContext ctx * Method called in cases where it looks like we got an Object Id * to parse and use as a reference. */ - protected Object _deserializeFromObjectId(JsonParser jp, DeserializationContext ctxt) throws IOException + protected Object _deserializeFromObjectId(JsonParser p, DeserializationContext ctxt) throws IOException { - Object id = _objectIdReader.readObjectReference(jp, ctxt); + Object id = _objectIdReader.readObjectReference(p, ctxt); ReadableObjectId roid = ctxt.findObjectId(id, _objectIdReader.generator, _objectIdReader.resolver); // do we have it resolved? Object pojo = roid.resolve(); if (pojo == null) { // not yet; should wait... - throw new UnresolvedForwardReference(jp, - "Could not resolve Object Id ["+id+"] -- unresolved forward-reference?", jp.getCurrentLocation(), roid); + throw new UnresolvedForwardReference(p, + "Could not resolve Object Id ["+id+"] -- unresolved forward-reference?", p.getCurrentLocation(), roid); } return pojo; } } - - diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java index ad585cd5a2..2c3260abb1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerBase.java @@ -982,7 +982,7 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, if (t == JsonToken.START_OBJECT) { t = p.nextToken(); } - if (t == JsonToken.FIELD_NAME && _objectIdReader.maySerializeAsObject() + if ((t == JsonToken.FIELD_NAME) && _objectIdReader.maySerializeAsObject() && _objectIdReader.isValidReferencePropertyName(p.getCurrentName(), p)) { return deserializeFromObjectId(p, ctxt); } @@ -1256,17 +1256,18 @@ public Object deserializeFromArray(JsonParser p, DeserializationContext ctxt) th throw ctxt.mappingException(handledType()); } - public Object deserializeFromEmbedded(JsonParser jp, DeserializationContext ctxt) throws IOException + public Object deserializeFromEmbedded(JsonParser p, DeserializationContext ctxt) + throws IOException { // First things first: id Object Id is used, most likely that's it; specifically, // true for UUIDs when written as binary (with Smile, other binary formats) if (_objectIdReader != null) { - return deserializeFromObjectId(jp, ctxt); + return deserializeFromObjectId(p, ctxt); } // TODO: maybe add support for ValueInstantiator, embedded? - return jp.getEmbeddedObject(); + return p.getEmbeddedObject(); } /* @@ -1276,7 +1277,7 @@ public Object deserializeFromEmbedded(JsonParser jp, DeserializationContext ctxt */ protected void injectValues(DeserializationContext ctxt, Object bean) - throws IOException, JsonProcessingException + throws IOException { for (ValueInjector injector : _injectables) { injector.inject(ctxt, bean); @@ -1291,11 +1292,11 @@ protected void injectValues(DeserializationContext ctxt, Object bean) @SuppressWarnings("resource") protected Object handleUnknownProperties(DeserializationContext ctxt, Object bean, TokenBuffer unknownTokens) - throws IOException, JsonProcessingException + throws IOException { // First: add closing END_OBJECT as marker unknownTokens.writeEndObject(); - + // note: buffer does NOT have starting START_OBJECT JsonParser bufferParser = unknownTokens.asParser(); while (bufferParser.nextToken() != JsonToken.END_OBJECT) { @@ -1311,22 +1312,22 @@ protected Object handleUnknownProperties(DeserializationContext ctxt, * Helper method called for an unknown property, when using "vanilla" * processing. */ - protected void handleUnknownVanilla(JsonParser jp, DeserializationContext ctxt, + protected void handleUnknownVanilla(JsonParser p, DeserializationContext ctxt, Object bean, String propName) - throws IOException, JsonProcessingException + throws IOException { if (_ignorableProps != null && _ignorableProps.contains(propName)) { - handleIgnoredProperty(jp, ctxt, bean, propName); + handleIgnoredProperty(p, ctxt, bean, propName); } else if (_anySetter != null) { try { // should we consider return type of any setter? - _anySetter.deserializeAndSet(jp, ctxt, bean, propName); + _anySetter.deserializeAndSet(p, ctxt, bean, propName); } catch (Exception e) { wrapAndThrow(e, bean, propName, ctxt); } } else { // Unknown: let's call handler method - handleUnknownProperty(jp, ctxt, bean, propName); + handleUnknownProperty(p, ctxt, bean, propName); } } @@ -1335,20 +1336,20 @@ protected void handleUnknownVanilla(JsonParser jp, DeserializationContext ctxt, * setter, any-setter or field, and thus can not be assigned. */ @Override - protected void handleUnknownProperty(JsonParser jp, DeserializationContext ctxt, + protected void handleUnknownProperty(JsonParser p, DeserializationContext ctxt, Object beanOrClass, String propName) - throws IOException, JsonProcessingException + throws IOException { if (_ignoreAllUnknown) { - jp.skipChildren(); + p.skipChildren(); return; } if (_ignorableProps != null && _ignorableProps.contains(propName)) { - handleIgnoredProperty(jp, ctxt, beanOrClass, propName); + handleIgnoredProperty(p, ctxt, beanOrClass, propName); } // Otherwise use default handling (call handler(s); if not // handled, throw exception or skip depending on settings) - super.handleUnknownProperty(jp, ctxt, beanOrClass, propName); + super.handleUnknownProperty(p, ctxt, beanOrClass, propName); } /** @@ -1357,14 +1358,14 @@ protected void handleUnknownProperty(JsonParser jp, DeserializationContext ctxt, * * @since 2.3 */ - protected void handleIgnoredProperty(JsonParser jp, DeserializationContext ctxt, + protected void handleIgnoredProperty(JsonParser p, DeserializationContext ctxt, Object beanOrClass, String propName) - throws IOException, JsonProcessingException + throws IOException { if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES)) { - throw IgnoredPropertyException.from(jp, beanOrClass, propName, getKnownPropertyNames()); + throw IgnoredPropertyException.from(p, beanOrClass, propName, getKnownPropertyNames()); } - jp.skipChildren(); + p.skipChildren(); } /** @@ -1374,14 +1375,14 @@ protected void handleIgnoredProperty(JsonParser jp, DeserializationContext ctxt, * class; either way, we may have more specific deserializer to use * for handling it. * - * @param jp (optional) If not null, parser that has more properties to handle + * @param p (optional) If not null, parser that has more properties to handle * (in addition to buffered properties); if null, all properties are passed * in buffer */ @SuppressWarnings("resource") - protected Object handlePolymorphic(JsonParser jp, DeserializationContext ctxt, + protected Object handlePolymorphic(JsonParser p, DeserializationContext ctxt, Object bean, TokenBuffer unknownTokens) - throws IOException, JsonProcessingException + throws IOException { // First things first: maybe there is a more specific deserializer available? JsonDeserializer subDeser = _findSubclassDeserializer(ctxt, bean, unknownTokens); @@ -1394,8 +1395,8 @@ protected Object handlePolymorphic(JsonParser jp, DeserializationContext ctxt, bean = subDeser.deserialize(p2, ctxt, bean); } // Original parser may also have some leftovers - if (jp != null) { - bean = subDeser.deserialize(jp, ctxt, bean); + if (p != null) { + bean = subDeser.deserialize(p, ctxt, bean); } return bean; } @@ -1404,8 +1405,8 @@ protected Object handlePolymorphic(JsonParser jp, DeserializationContext ctxt, bean = handleUnknownProperties(ctxt, bean, unknownTokens); } // and/or things left to process via main parser? - if (jp != null) { - bean = deserialize(jp, ctxt, bean); + if (p != null) { + bean = deserialize(p, ctxt, bean); } return bean; } @@ -1416,7 +1417,7 @@ protected Object handlePolymorphic(JsonParser jp, DeserializationContext ctxt, */ protected JsonDeserializer _findSubclassDeserializer(DeserializationContext ctxt, Object bean, TokenBuffer unknownTokens) - throws IOException, JsonProcessingException + throws IOException { JsonDeserializer subDeser; diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumMapSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumMapSerializer.java deleted file mode 100644 index ea6b3b4947..0000000000 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/EnumMapSerializer.java +++ /dev/null @@ -1,378 +0,0 @@ -package com.fasterxml.jackson.databind.ser.std; - -import java.io.IOException; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.*; - -import com.fasterxml.jackson.core.*; -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; -import com.fasterxml.jackson.databind.introspect.AnnotatedMember; -import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable; -import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; -import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor; -import com.fasterxml.jackson.databind.jsonschema.SchemaAware; -import com.fasterxml.jackson.databind.jsontype.TypeSerializer; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.ser.ContainerSerializer; -import com.fasterxml.jackson.databind.ser.ContextualSerializer; -import com.fasterxml.jackson.databind.util.EnumValues; - -/** - * Specialized serializer for {@link EnumMap}s. Somewhat tricky to - * implement because actual Enum value type may not be available; - * and if not, it can only be gotten from actual instance. - * - * @deprecated Since 2.4.4; standard {@link MapSerializer} works better. - * (to be removed from 2.6) - */ -@SuppressWarnings("serial") -@JacksonStdImpl -@Deprecated -public class EnumMapSerializer - extends ContainerSerializer, ?>> - implements ContextualSerializer -{ - protected final boolean _staticTyping; - - /** - * Property for which this serializer is being used, if any; - * null for root values. - */ - protected final BeanProperty _property; - - /** - * If we know enumeration used as key, this will contain - * value set to use for serialization - */ - protected final EnumValues _keyEnums; - - protected final JavaType _valueType; - - /** - * Value serializer to use, if it can be statically determined - */ - protected final JsonSerializer _valueSerializer; - - /** - * Type serializer used for values, if any. - */ - protected final TypeSerializer _valueTypeSerializer; - - /* - /********************************************************** - /* Life-cycle - /********************************************************** - */ - - public EnumMapSerializer(JavaType valueType, boolean staticTyping, EnumValues keyEnums, - TypeSerializer vts, JsonSerializer valueSerializer) - { - super(EnumMap.class, false); - _property = null; // not yet known - _staticTyping = staticTyping || (valueType != null && valueType.isFinal()); - _valueType = valueType; - _keyEnums = keyEnums; - _valueTypeSerializer = vts; - _valueSerializer = valueSerializer; - } - - /** - * Constructor called when a contextual instance is created. - */ - @SuppressWarnings("unchecked") - public EnumMapSerializer(EnumMapSerializer src, BeanProperty property, - JsonSerializer ser) - { - super(src); - _property = property; - _staticTyping = src._staticTyping; - _valueType = src._valueType; - _keyEnums = src._keyEnums; - _valueTypeSerializer = src._valueTypeSerializer; - _valueSerializer = (JsonSerializer) ser; - } - - @Override - public EnumMapSerializer _withValueTypeSerializer(TypeSerializer vts) { - return new EnumMapSerializer(_valueType, _staticTyping, _keyEnums, vts, _valueSerializer); - } - - public EnumMapSerializer withValueSerializer(BeanProperty prop, JsonSerializer ser) { - if (_property == prop && ser == _valueSerializer) { - return this; - } - return new EnumMapSerializer(this, prop, ser); - } - - @Override - public JsonSerializer createContextual(SerializerProvider provider, - BeanProperty property) - throws JsonMappingException - { - /* 29-Sep-2012, tatu: Actually, we need to do much more contextual - * checking here since we finally know for sure the property, - * and it may have overrides - */ - JsonSerializer ser = null; - // First: if we have a property, may have property-annotation overrides - if (property != null) { - AnnotatedMember m = property.getMember(); - if (m != null) { - Object serDef = provider.getAnnotationIntrospector().findContentSerializer(m); - if (serDef != null) { - ser = provider.serializerInstance(m, serDef); - } - } - } - if (ser == null) { - ser = _valueSerializer; - } - // #124: May have a content converter - ser = findConvertingContentSerializer(provider, property, ser); - if (ser == null) { - if (_staticTyping) { - return withValueSerializer(property, provider.findValueSerializer(_valueType, property)); - } - } else { - ser = provider.handleSecondaryContextualization(ser, property); - } - if (ser != _valueSerializer) { - return withValueSerializer(property, ser); - } - return this; - } - - /* - /********************************************************** - /* Accessors - /********************************************************** - */ - - @Override - public JavaType getContentType() { - return _valueType; - } - - @Override - public JsonSerializer getContentSerializer() { - return _valueSerializer; - } - - @Override - public boolean isEmpty(SerializerProvider prov, EnumMap,?> value) { - return (value == null) || value.isEmpty(); - } - - @Override - public boolean hasSingleElement(EnumMap, ?> value) { - return value.size() == 1; - } - - /* - /********************************************************** - /* Serialization - /********************************************************** - */ - - @Override - public void serialize(EnumMap,?> value, JsonGenerator jgen, SerializerProvider provider) - throws IOException, JsonGenerationException - { - jgen.writeStartObject(); - if (!value.isEmpty()) { - serializeContents(value, jgen, provider); - } - jgen.writeEndObject(); - } - - @Override - public void serializeWithType(EnumMap,?> value, JsonGenerator jgen, SerializerProvider provider, - TypeSerializer typeSer) - throws IOException, JsonGenerationException - { - typeSer.writeTypePrefixForObject(value, jgen); - if (!value.isEmpty()) { - serializeContents(value, jgen, provider); - } - typeSer.writeTypeSuffixForObject(value, jgen); - } - - protected void serializeContents(EnumMap,?> value, JsonGenerator jgen, SerializerProvider provider) - throws IOException, JsonGenerationException - { - if (_valueSerializer != null) { - serializeContentsUsing(value, jgen, provider, _valueSerializer); - return; - } - JsonSerializer prevSerializer = null; - Class prevClass = null; - EnumValues keyEnums = _keyEnums; - final boolean skipNulls = !provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES); - final boolean useToString = provider.isEnabled(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); - final TypeSerializer vts = _valueTypeSerializer; - - for (Map.Entry,?> entry : value.entrySet()) { - final Object valueElem = entry.getValue(); - if (skipNulls && valueElem == null) { // [JACKSON-314] skip entries with null values? - continue; - } - // First, serialize key - final Enum key = entry.getKey(); - if (useToString) { - jgen.writeFieldName(key.toString()); - } else { - if (keyEnums == null) { - /* 15-Oct-2009, tatu: This is clumsy, but still the simplest efficient - * way to do it currently, as Serializers get cached. (it does assume we'll always use - * default serializer tho -- so ideally code should be rewritten) - */ - // ... and lovely two-step casting process too... - StdSerializer ser = (StdSerializer) provider.findValueSerializer( - key.getDeclaringClass(), _property); - keyEnums = ((EnumSerializer) ser).getEnumValues(); - } - jgen.writeFieldName(keyEnums.serializedValueFor(key)); - } - if (valueElem == null) { - provider.defaultSerializeNull(jgen); - continue; - } - Class cc = valueElem.getClass(); - JsonSerializer currSerializer; - if (cc == prevClass) { - currSerializer = prevSerializer; - } else { - currSerializer = provider.findValueSerializer(cc, _property); - prevSerializer = currSerializer; - prevClass = cc; - } - try { - if (vts == null) { - currSerializer.serialize(valueElem, jgen, provider); - } else { - currSerializer.serializeWithType(valueElem, jgen, provider, vts); - } - } catch (Exception e) { - // [JACKSON-55] Need to add reference information - wrapAndThrow(provider, e, value, entry.getKey().name()); - } - } - } - - protected void serializeContentsUsing(EnumMap,?> value, JsonGenerator jgen, SerializerProvider provider, - JsonSerializer valueSer) - throws IOException, JsonGenerationException - { - EnumValues keyEnums = _keyEnums; - final boolean skipNulls = !provider.isEnabled(SerializationFeature.WRITE_NULL_MAP_VALUES); - final boolean useToString = provider.isEnabled(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); - final TypeSerializer vts = _valueTypeSerializer; - - for (Map.Entry,?> entry : value.entrySet()) { - final Object valueElem = entry.getValue(); - if (skipNulls && valueElem == null) { // [JACKSON-314] skip entries with null values? - continue; - } - Enum key = entry.getKey(); - if (useToString) { - jgen.writeFieldName(key.toString()); - } else { - if (keyEnums == null) { - // clumsy, but has to do for now: - StdSerializer ser = (StdSerializer) provider.findValueSerializer(key.getDeclaringClass(), - _property); - keyEnums = ((EnumSerializer) ser).getEnumValues(); - } - jgen.writeFieldName(keyEnums.serializedValueFor(key)); - } - if (valueElem == null) { - provider.defaultSerializeNull(jgen); - continue; - } - try { - if (vts == null) { - valueSer.serialize(valueElem, jgen, provider); - } else { - valueSer.serializeWithType(valueElem, jgen, provider, vts); - } - } catch (Exception e) { - wrapAndThrow(provider, e, value, entry.getKey().name()); - } - } - } - - @SuppressWarnings({ "unchecked" }) - @Override - public JsonNode getSchema(SerializerProvider provider, Type typeHint) - throws JsonMappingException - { - ObjectNode o = createSchemaNode("object", true); - if (typeHint instanceof ParameterizedType) { - Type[] typeArgs = ((ParameterizedType) typeHint).getActualTypeArguments(); - if (typeArgs.length == 2) { - JavaType enumType = provider.constructType(typeArgs[0]); - JavaType valueType = provider.constructType(typeArgs[1]); - ObjectNode propsNode = JsonNodeFactory.instance.objectNode(); - Class> enumClass = (Class>) enumType.getRawClass(); - for (Enum enumValue : enumClass.getEnumConstants()) { - JsonSerializer ser = provider.findValueSerializer(valueType.getRawClass(), _property); - JsonNode schemaNode = (ser instanceof SchemaAware) ? - ((SchemaAware) ser).getSchema(provider, null) : - com.fasterxml.jackson.databind.jsonschema.JsonSchema.getDefaultSchemaNode(); - propsNode.set(provider.getConfig().getAnnotationIntrospector().findEnumValue((Enum)enumValue), schemaNode); - } - o.set("properties", propsNode); - } - } - return o; - } - - /** - * We consider possibility here that an EnumMap might actually just be serialized - * as something like a Record, given that number of keys is bound, just like - * with Objects/Records (and not unbounded like regular maps) - */ - @Override - public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) - throws JsonMappingException - { - if (visitor == null) { - return; - } - JsonObjectFormatVisitor objectVisitor = visitor.expectObjectFormat(typeHint); - if (objectVisitor == null) { - return; - } - JavaType valueType = typeHint.containedType(1); - JsonSerializer ser = _valueSerializer; - if (ser == null && valueType != null) { - ser = visitor.getProvider().findValueSerializer(valueType, _property); - } - if (valueType == null) { - valueType = visitor.getProvider().constructType(Object.class); - } - EnumValues keyEnums = _keyEnums; - if (keyEnums == null) { - JavaType enumType = typeHint.containedType(0); - if (enumType == null) { - throw new IllegalStateException("Can not resolve Enum type of EnumMap: "+typeHint); - } - JsonSerializer enumSer = visitor.getProvider().findValueSerializer(enumType, _property); - if (!(enumSer instanceof EnumSerializer)) { - throw new IllegalStateException("Can not resolve Enum type of EnumMap: "+typeHint); - } - keyEnums = ((EnumSerializer) enumSer).getEnumValues(); - } - for (Map.Entry entry : keyEnums.internalMap().entrySet()) { - String name = entry.getValue().getValue(); - // should all have the same type, so: - if (ser == null) { - ser = visitor.getProvider().findValueSerializer(entry.getKey().getClass(), _property); - } - objectVisitor.property(name, (JsonFormatVisitable) ser, valueType); - } - } -} diff --git a/src/test/java/com/fasterxml/jackson/databind/deprecated/MiscDeprecatedTest.java b/src/test/java/com/fasterxml/jackson/databind/deprecated/MiscDeprecatedTest.java deleted file mode 100644 index 3fe9d0ebe0..0000000000 --- a/src/test/java/com/fasterxml/jackson/databind/deprecated/MiscDeprecatedTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.fasterxml.jackson.databind.deprecated; - -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.ser.std.EnumMapSerializer; -import com.fasterxml.jackson.databind.util.EnumValues; - -@SuppressWarnings("deprecation") -public class MiscDeprecatedTest extends BaseMapTest -{ - private final ObjectMapper MAPPER = objectMapper(); - - @SuppressWarnings("unchecked") - public void testOldEnumMapSerializer() throws Exception - { - /* - public EnumMapSerializer(JavaType valueType, boolean staticTyping, EnumValues keyEnums, - TypeSerializer vts, JsonSerializer valueSerializer) - */ - // to be removed from 2.7 or so: - Class enumClass = ABC.class; - EnumMapSerializer ser = new EnumMapSerializer(MAPPER.constructType(String.class), true, - EnumValues.construct(MAPPER.getSerializationConfig(), (Class>) enumClass), - null, /* value serializer */ null); - - // ... and? - assertNotNull(ser); - } -} diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestObjectNode.java b/src/test/java/com/fasterxml/jackson/databind/node/TestObjectNode.java index 6229f2c11a..719c109832 100644 --- a/src/test/java/com/fasterxml/jackson/databind/node/TestObjectNode.java +++ b/src/test/java/com/fasterxml/jackson/databind/node/TestObjectNode.java @@ -403,12 +403,14 @@ public void testIssue941() throws Exception ObjectNode object = MAPPER.createObjectNode(); String json = MAPPER.writeValueAsString(object); - System.out.println("json: "+json); +// System.out.println("json: "+json); ObjectNode de1 = MAPPER.readValue(json, ObjectNode.class); // this works - System.out.println("Deserialized to ObjectNode: "+de1); +// System.out.println("Deserialized to ObjectNode: "+de1); + assertNotNull(de1); MyValue de2 = MAPPER.readValue(json, MyValue.class); // but this throws exception - System.out.println("Deserialized to MyValue: "+de2); +// System.out.println("Deserialized to MyValue: "+de2); + assertNotNull(de2); } } From 91b52bcd0a2919e4d4882dd224163ecf16f1739b Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 6 Dec 2015 16:10:45 -0800 Subject: [PATCH 051/124] A minor improvement to sync handling of AnnotatedClass --- .../databind/introspect/AnnotatedClass.java | 69 +++++++++---------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java index b91ccd6154..52efca0d21 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java @@ -244,20 +244,13 @@ public JavaType resolveType(Type type) { public String getName() { return _class.getName(); } @Override - public A getAnnotation(Class acls) - { - if (_classAnnotations == null) { - resolveClassAnnotations(); - } - return _classAnnotations.get(acls); + public A getAnnotation(Class acls) { + return _classAnnotations().get(acls); } @Override public boolean hasAnnotation(Class acls) { - if (_classAnnotations == null) { - resolveClassAnnotations(); - } - return _classAnnotations.has(acls); + return _classAnnotations().has(acls); } @Override @@ -267,18 +260,12 @@ public Class getRawType() { @Override public Iterable annotations() { - if (_classAnnotations == null) { - resolveClassAnnotations(); - } - return _classAnnotations.annotations(); + return _classAnnotations().annotations(); } @Override protected AnnotationMap getAllAnnotations() { - if (_classAnnotations == null) { - resolveClassAnnotations(); - } - return _classAnnotations; + return _classAnnotations(); } @Override @@ -293,17 +280,11 @@ public JavaType getType() { */ public Annotations getAnnotations() { - if (_classAnnotations == null) { - resolveClassAnnotations(); - } - return _classAnnotations; + return _classAnnotations(); } - + public boolean hasAnnotations() { - if (_classAnnotations == null) { - resolveClassAnnotations(); - } - return _classAnnotations.size() > 0; + return _classAnnotations().size() > 0; } public AnnotatedConstructor getDefaultConstructor() @@ -375,29 +356,46 @@ public Iterable fields() /********************************************************** */ + private AnnotationMap _classAnnotations() { + AnnotationMap anns = _classAnnotations; + if (anns == null) { + // yes, double-locking, typically not a good choice. But for typical usage + // pattern here (and with JVM 7 and above) is a reasonable choice to avoid + // non-common but existing race condition from root name lookup style usage + synchronized (this) { + anns = _classAnnotations; + if (anns == null) { + anns = _resolveClassAnnotations(); + _classAnnotations = anns; + } + } + } + return anns; + } + /** * Initialization method that will recursively collect Jackson * annotations for this class and all super classes and * interfaces. */ - private void resolveClassAnnotations() + private AnnotationMap _resolveClassAnnotations() { - _classAnnotations = new AnnotationMap(); + AnnotationMap ca = new AnnotationMap(); // Should skip processing if annotation processing disabled if (_annotationIntrospector != null) { // add mix-in annotations first (overrides) if (_primaryMixIn != null) { - _addClassMixIns(_classAnnotations, _class, _primaryMixIn); + _addClassMixIns(ca, _class, _primaryMixIn); } // first, annotations from the class itself: - _addAnnotationsIfNotPresent(_classAnnotations, + _addAnnotationsIfNotPresent(ca, ClassUtil.findClassAnnotations(_class)); // and then from super types for (JavaType type : _superTypes) { // and mix mix-in annotations in-between - _addClassMixIns(_classAnnotations, type); - _addAnnotationsIfNotPresent(_classAnnotations, + _addClassMixIns(ca, type); + _addAnnotationsIfNotPresent(ca, ClassUtil.findClassAnnotations(type.getRawClass())); } /* and finally... any annotations there might be for plain @@ -407,10 +405,11 @@ private void resolveClassAnnotations() /* 12-Jul-2009, tatu: Should this be done for interfaces too? * For now, yes, seems useful for some cases, and not harmful for any? */ - _addClassMixIns(_classAnnotations, Object.class); + _addClassMixIns(ca, Object.class); } + return ca; } - + /** * Initialization method that will find out all constructors * and potential static factory methods the class has. From ccf82e88ebb2f68666a866d39c066d14a700544e Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 6 Dec 2015 16:26:47 -0800 Subject: [PATCH 052/124] Minor improvement to handling of AnnotatedClass, wrt lazy resolution --- .../jackson/databind/introspect/AnnotatedClass.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java index 61151a9642..422e1d2764 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedClass.java @@ -299,21 +299,21 @@ public Iterable fields() */ private void resolveClassAnnotations() { - _classAnnotations = new AnnotationMap(); + AnnotationMap ca = new AnnotationMap(); // [JACKSON-659] Should skip processing if annotation processing disabled if (_annotationIntrospector != null) { // add mix-in annotations first (overrides) if (_primaryMixIn != null) { - _addClassMixIns(_classAnnotations, _class, _primaryMixIn); + _addClassMixIns(ca, _class, _primaryMixIn); } // first, annotations from the class itself: - _addAnnotationsIfNotPresent(_classAnnotations, _class.getDeclaredAnnotations()); + _addAnnotationsIfNotPresent(ca, _class.getDeclaredAnnotations()); // and then from super types for (Class cls : _superTypes) { // and mix mix-in annotations in-between - _addClassMixIns(_classAnnotations, cls); - _addAnnotationsIfNotPresent(_classAnnotations, cls.getDeclaredAnnotations()); + _addClassMixIns(ca, cls); + _addAnnotationsIfNotPresent(ca, cls.getDeclaredAnnotations()); } /* and finally... any annotations there might be for plain * old Object.class: separate because for all other purposes @@ -322,8 +322,9 @@ private void resolveClassAnnotations() /* 12-Jul-2009, tatu: Should this be done for interfaces too? * For now, yes, seems useful for some cases, and not harmful for any? */ - _addClassMixIns(_classAnnotations, Object.class); + _addClassMixIns(ca, Object.class); } + _classAnnotations = ca; } /** From 3be8f745b938d5e82a5a322f51a2244a6143617a Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 6 Dec 2015 16:33:38 -0800 Subject: [PATCH 053/124] ... --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5078548122..4f407d0aa9 100644 --- a/pom.xml +++ b/pom.xml @@ -76,7 +76,7 @@ javax.xml.datatype, javax.xml.namespace, javax.xml.parsers com.fasterxml.jackson.core jackson-core - 2.6.3 + 2.6.4-SNAPSHOT - 2.7.0-rc2-SNAPSHOT + + 2.7.0-rc2 com.fasterxml.jackson.core From 5df1a6c3bd4f1594b945ea6fda70fe43e87e5e29 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 11 Dec 2015 17:13:59 -0800 Subject: [PATCH 071/124] Fix #1044 --- release-notes/VERSION | 2 + .../databind/AnnotationIntrospector.java | 18 ++++++ .../databind/JsonMappingException.java | 5 +- .../AnnotationIntrospectorPair.java | 14 ++++- .../JacksonAnnotationIntrospector.java | 28 ++++++++++ .../introspect/POJOPropertiesCollector.java | 9 ++- .../introspect/POJOPropertyBuilder.java | 55 +++++++++++++------ .../introspect/SetterConflictTest.java | 32 +++++++++++ .../introspect/TestPropertyConflicts.java | 13 ++--- .../databind/ser/TestBeanSerializer.java | 2 +- 10 files changed, 145 insertions(+), 33 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/introspect/SetterConflictTest.java diff --git a/release-notes/VERSION b/release-notes/VERSION index df87f993af..807a962990 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -52,6 +52,8 @@ Project: jackson-databind (suggested by David B) #1043: @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) does not work on fields (reported by fabiolaa@github) +#1044: Add `AnnotationIntrospector.resolveSetterConflict(...)` to allow custom setter conflict resolution + (suggested by clydebarrow@github) - Make `JsonValueFormat` (self-)serializable, deserializable, to/from valid external value (as per JSON Schema spec) diff --git a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java index 4d6fb81a21..10289ba9fe 100644 --- a/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/AnnotationIntrospector.java @@ -30,6 +30,11 @@ * so that different sets of annotations can be supported, and support * plugged-in dynamically. *

    + * Although default implementations are based on using annotations as the only + * (or at least main) information source, custom implementations are not limited + * in such a way, and in fact there is no expectation they should be. So the name + * is bit of misnomer; this is a general configuration introspection facility. + *

    * NOTE: due to rapid addition of new methods (and changes to existing methods), * it is strongly recommended that custom implementations should not directly * extend this class, but rather extend {@link NopAnnotationIntrospector}. @@ -559,6 +564,19 @@ public TypeResolverBuilder findPropertyContentTypeResolver(MapperConfig co */ public JsonProperty.Access findPropertyAccess(Annotated ann) { return null; } + /** + * Method called in cases where a class has two methods eligible to be used + * for the same logical property, and default logic is not enough to figure + * out clear precedence. Introspector may try to choose one to use; or, if + * unable, return `null` to indicate it can not resolve the problem. + * + * @since 2.7 + */ + public AnnotatedMethod resolveSetterConflict(MapperConfig config, + AnnotatedMethod setter1, AnnotatedMethod setter2) { + return null; + } + /* /********************************************************** /* Serialization: general annotations diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonMappingException.java b/src/main/java/com/fasterxml/jackson/databind/JsonMappingException.java index df0f3d20a6..4ff6c02c57 100644 --- a/src/main/java/com/fasterxml/jackson/databind/JsonMappingException.java +++ b/src/main/java/com/fasterxml/jackson/databind/JsonMappingException.java @@ -10,7 +10,10 @@ /** * Checked exception used to signal fatal problems with mapping of - * content. + * content, distinct from low-level I/O problems (signaled using + * simple {@link java.io.IOException}s) or data encoding/decoding + * problems (signaled with {@link com.fasterxml.jackson.core.JsonParseException}, + * {@link com.fasterxml.jackson.core.JsonGenerationException}). *

    * One additional feature is the ability to denote relevant path * of references (during serialization/deserialization) to help in diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java index 6f83e0d267..a7bfa3fe6c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotationIntrospectorPair.java @@ -467,10 +467,20 @@ public JsonProperty.Access findPropertyAccess(Annotated ann) { return JsonProperty.Access.AUTO; } + @Override // since 2.7 + public AnnotatedMethod resolveSetterConflict(MapperConfig config, + AnnotatedMethod setter1, AnnotatedMethod setter2) + { + AnnotatedMethod res = _primary.resolveSetterConflict(config, setter1, setter2); + if (res == null) { + res = _secondary.resolveSetterConflict(config, setter1, setter2); + } + return res; + } + // // // Serialization: type refinements - // since 2.7 - @Override + @Override // since 2.7 public JavaType refineSerializationType(MapperConfig config, Annotated a, JavaType baseType) throws JsonMappingException { diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index f101232c13..0e55b675b2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -414,6 +414,34 @@ public Class[] findViews(Annotated a) return (ann == null) ? null : ann.value(); } + @Override // since 2.7 + public AnnotatedMethod resolveSetterConflict(MapperConfig config, + AnnotatedMethod setter1, AnnotatedMethod setter2) + { + Class cls1 = setter1.getRawParameterType(0); + Class cls2 = setter2.getRawParameterType(0); + + // First: prefer primitives over non-primitives + // 11-Dec-2015, tatu: TODO, perhaps consider wrappers for primitives too? + if (cls1.isPrimitive()) { + if (!cls2.isPrimitive()) { + return setter1; + } + } else if (cls2.isPrimitive()) { + return setter2; + } + + if (cls1 == String.class) { + if (cls2 != String.class) { + return setter1; + } + } else if (cls2 == String.class) { + return setter2; + } + + return null; + } + /* /********************************************************** /* Annotations for Polymorphic Type handling 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 6d19f6b90a..dd4733f528 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertiesCollector.java @@ -784,9 +784,8 @@ protected void _renameUsing(Map propMap, for (POJOPropertyBuilder prop : props) { PropertyName fullName = prop.getFullName(); String rename = null; - // As per [#428](https://github.com/FasterXML/jackson-databind/issues/428) need - // to skip renaming if property has explicitly defined name, unless feature - // is enabled + // As per [databind#428] need to skip renaming if property has + // explicitly defined name, unless feature is enabled if (!prop.isExplicitlyNamed() || _config.isEnabled(MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING)) { if (_forSerialization) { if (prop.hasGetter()) { @@ -983,8 +982,8 @@ protected POJOPropertyBuilder _property(Map props, { POJOPropertyBuilder prop = props.get(implName); if (prop == null) { - prop = new POJOPropertyBuilder(PropertyName.construct(implName), - _annotationIntrospector, _forSerialization); + prop = new POJOPropertyBuilder(_config, _annotationIntrospector, _forSerialization, + PropertyName.construct(implName)); props.put(implName, prop); } return prop; diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java index 51e5b872b1..62bcb5eef3 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/POJOPropertyBuilder.java @@ -4,8 +4,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; - import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.cfg.MapperConfig; import com.fasterxml.jackson.databind.util.ClassUtil; /** @@ -22,6 +22,8 @@ public class POJOPropertyBuilder */ protected final boolean _forSerialization; + protected final MapperConfig _config; + protected final AnnotationIntrospector _annotationIntrospector; /** @@ -45,25 +47,27 @@ public class POJOPropertyBuilder protected Linked _setters; - public POJOPropertyBuilder(PropertyName internalName, AnnotationIntrospector ai, - boolean forSerialization) { - this(internalName, internalName, ai, forSerialization); + public POJOPropertyBuilder(MapperConfig config, AnnotationIntrospector ai, + boolean forSerialization, PropertyName internalName) { + this(config, ai, forSerialization, internalName, internalName); } - protected POJOPropertyBuilder(PropertyName internalName, PropertyName name, - AnnotationIntrospector annotationIntrospector, boolean forSerialization) + protected POJOPropertyBuilder(MapperConfig config, AnnotationIntrospector ai, + boolean forSerialization, PropertyName internalName, PropertyName name) { + _config = config; + _annotationIntrospector = ai; _internalName = internalName; _name = name; - _annotationIntrospector = annotationIntrospector; _forSerialization = forSerialization; } public POJOPropertyBuilder(POJOPropertyBuilder src, PropertyName newName) { + _config = src._config; + _annotationIntrospector = src._annotationIntrospector; _internalName = src._internalName; _name = newName; - _annotationIntrospector = src._annotationIntrospector; _fields = src._fields; _ctorParameters = src._ctorParameters; _getters = src._getters; @@ -269,9 +273,7 @@ public AnnotatedMethod getSetter() } // But if multiple, verify that they do not conflict... for (; next != null; next = next.next) { - /* [JACKSON-255] Allow masking, i.e. do not report exception if one - * is in super-class from the other - */ + // Allow masking, i.e. do not fail if one is in super-class from the other Class currClass = curr.value.getDeclaringClass(); Class nextClass = next.value.getDeclaringClass(); if (currClass != nextClass) { @@ -283,13 +285,16 @@ public AnnotatedMethod getSetter() continue; } } + AnnotatedMethod nextM = next.value; + AnnotatedMethod currM = curr.value; + /* 30-May-2014, tatu: Two levels of precedence: * * 1. Regular setters ("setX(...)") * 2. Implicit, possible setters ("x(...)") */ - int priNext = _setterPriority(next.value); - int priCurr = _setterPriority(curr.value); + int priNext = _setterPriority(nextM); + int priCurr = _setterPriority(currM); if (priNext != priCurr) { if (priNext < priCurr) { @@ -297,6 +302,21 @@ public AnnotatedMethod getSetter() } continue; } + // 11-Dec-2015, tatu: As per [databind#1033] allow pluggable conflict resolution + if (_annotationIntrospector != null) { + AnnotatedMethod pref = _annotationIntrospector.resolveSetterConflict(_config, + currM, nextM); + + // note: should be one of nextM/currM; but no need to check + if (pref == currM) { + continue; + } + if (pref == nextM) { + curr = next; + continue; + } + } + throw new IllegalArgumentException("Conflicting setter definitions for property \""+getName()+"\": " +curr.value.getFullName()+" vs "+next.value.getFullName()); } @@ -408,7 +428,7 @@ public AnnotatedMember getPrimaryMember() { protected int _getterPriority(AnnotatedMethod m) { final String name = m.getName(); - // [#238]: Also, regular getters have precedence over "is-getters" + // [databind#238]: Also, regular getters have precedence over "is-getters" if (name.startsWith("get") && name.length() > 3) { // should we check capitalization? return 1; @@ -428,7 +448,7 @@ protected int _setterPriority(AnnotatedMethod m) } return 2; } - + /* /********************************************************** /* Implementations of refinement accessors @@ -885,7 +905,7 @@ private void _explode(Collection newNames, for (Linked node = accessors; node != null; node = node.next) { PropertyName name = node.name; if (!node.isNameExplicit || name == null) { // no explicit name -- problem! - // [Issue#541] ... but only as long as it's visible + // [databind#541] ... but only as long as it's visible if (!node.isVisible) { continue; } @@ -896,7 +916,8 @@ private void _explode(Collection newNames, } POJOPropertyBuilder prop = props.get(name); if (prop == null) { - prop = new POJOPropertyBuilder(_internalName, name, _annotationIntrospector, _forSerialization); + prop = new POJOPropertyBuilder(_config, _annotationIntrospector, _forSerialization, + _internalName, name); props.put(name, prop); } // ultra-clumsy, part 2 -- lambdas would be nice here diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/SetterConflictTest.java b/src/test/java/com/fasterxml/jackson/databind/introspect/SetterConflictTest.java new file mode 100644 index 0000000000..34820cece6 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/SetterConflictTest.java @@ -0,0 +1,32 @@ +package com.fasterxml.jackson.databind.introspect; + +import com.fasterxml.jackson.databind.*; + +// mostly for [databind#1033] +public class SetterConflictTest extends BaseMapTest +{ + // Should prefer primitives over Strings, more complex types, by default + static class Issue1033Bean { + public int value; + + public void setValue(int v) { value = v; } + public void setValue(Issue1033Bean foo) { + throw new Error("Should not get called"); + } + } + + /* + /********************************************************** + /* Test methods + /********************************************************** + */ + + private final ObjectMapper MAPPER = objectMapper(); + + public void testSetterPriority() throws Exception + { + Issue1033Bean bean = MAPPER.readValue(aposToQuotes("{'value':42}"), + Issue1033Bean.class); + assertEquals(42, bean.value); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/introspect/TestPropertyConflicts.java b/src/test/java/com/fasterxml/jackson/databind/introspect/TestPropertyConflicts.java index 16942b851c..5bd431ae75 100644 --- a/src/test/java/com/fasterxml/jackson/databind/introspect/TestPropertyConflicts.java +++ b/src/test/java/com/fasterxml/jackson/databind/introspect/TestPropertyConflicts.java @@ -12,14 +12,14 @@ */ public class TestPropertyConflicts extends BaseMapTest { - // For [JACKSON-694]: error message for conflicting getters sub-optimal + // error message for conflicting getters sub-optimal static class BeanWithConflict { public int getX() { return 3; } public boolean getx() { return false; } } - // [Issue#238] + // [databind#238] protected static class Getters1A { @JsonProperty @@ -67,7 +67,7 @@ public void _stuff(String value) { } } - // For [Issue#541] + // For [databind#541] static class Bean541 { protected String str; @@ -81,14 +81,13 @@ public String getStr() { return str; } } - + /* /********************************************************** /* Test methods /********************************************************** */ - - // for [JACKSON-694] + public void testFailWithDupProps() throws Exception { BeanWithConflict bean = new BeanWithConflict(); @@ -100,7 +99,7 @@ public void testFailWithDupProps() throws Exception } } - // [Issue#238]: ok to have getter, "isGetter" + // [databind#238]: ok to have getter, "isGetter" public void testRegularAndIsGetter() throws Exception { final ObjectWriter writer = objectWriter(); diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestBeanSerializer.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestBeanSerializer.java index 191bc08856..915a2abf3b 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/TestBeanSerializer.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestBeanSerializer.java @@ -157,7 +157,7 @@ public List changeProperties(SerializationConfig config, { JavaType strType = config.constructType(String.class); // we need a valid BeanPropertyDefinition; this will do (just need name to match) - POJOPropertyBuilder prop = new POJOPropertyBuilder(new PropertyName("bogus"), null, true); + POJOPropertyBuilder prop = new POJOPropertyBuilder(config, null, true, new PropertyName("bogus")); try { AnnotatedField f = new AnnotatedField(null, EmptyBean.class.getDeclaredField("name"), null); beanProperties.add(new BeanPropertyWriter(prop, f, null, From 167baee7248bd283eec57db5d6a6d59d6ce14ad1 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 11 Dec 2015 17:54:26 -0800 Subject: [PATCH 072/124] prepare for 2.7.0-rc2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 01f9f88e85..593020325e 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ com.fasterxml.jackson.core jackson-core - 2.7.0-rc2-SNAPSHOT + 2.7.0-rc2 - 2.7.0-rc2 + 2.7.0-rc3-SNAPSHOT com.fasterxml.jackson.core diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 2822249e3f..fc1a2bacab 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -3628,6 +3628,7 @@ protected final void _configAndWriteValue(JsonGenerator g, Object value) } } + @Deprecated // since 2.7, not used internally any more protected final void _configAndWriteValue(JsonGenerator g, Object value, Class viewClass) throws IOException { @@ -3661,7 +3662,7 @@ protected final void _configAndWriteValue(JsonGenerator g, Object value, Class Date: Mon, 28 Dec 2015 16:45:27 -0800 Subject: [PATCH 094/124] ... --- src/main/java/com/fasterxml/jackson/databind/JsonNode.java | 2 +- .../com/fasterxml/jackson/databind/node/MissingNode.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonNode.java b/src/main/java/com/fasterxml/jackson/databind/JsonNode.java index c8123f25e1..e1c472be79 100644 --- a/src/main/java/com/fasterxml/jackson/databind/JsonNode.java +++ b/src/main/java/com/fasterxml/jackson/databind/JsonNode.java @@ -231,7 +231,7 @@ public final JsonNode at(String jsonPtrExpr) { } protected abstract JsonNode _at(JsonPointer ptr); - + /* /********************************************************** /* Public API, type introspection diff --git a/src/main/java/com/fasterxml/jackson/databind/node/MissingNode.java b/src/main/java/com/fasterxml/jackson/databind/node/MissingNode.java index 9bb55e608e..4e18fff2b2 100644 --- a/src/main/java/com/fasterxml/jackson/databind/node/MissingNode.java +++ b/src/main/java/com/fasterxml/jackson/databind/node/MissingNode.java @@ -30,7 +30,7 @@ private MissingNode() { } @SuppressWarnings("unchecked") @Override public T deepCopy() { return (T) this; } - + public static MissingNode getInstance() { return instance; } @Override @@ -68,11 +68,11 @@ public final void serialize(JsonGenerator jg, SerializerProvider provider) } @Override - public void serializeWithType(JsonGenerator jg, SerializerProvider provider, + public void serializeWithType(JsonGenerator g, SerializerProvider provider, TypeSerializer typeSer) throws IOException, JsonProcessingException { - jg.writeNull(); + g.writeNull(); } @Override From 1a8b5d3b6c19d91e0e39921e9fed0d01f5140a20 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 29 Dec 2015 20:13:21 -0800 Subject: [PATCH 095/124] ... --- .../java/com/fasterxml/jackson/databind/node/ObjectNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java b/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java index e819facbce..14923f1a9d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java +++ b/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java @@ -32,7 +32,7 @@ public ObjectNode(JsonNodeFactory nc, Map kids) { super(nc); _children = kids; } - + @Override protected JsonNode _at(JsonPointer ptr) { return get(ptr.getMatchingProperty()); From 7e30b06349586381fe72e0fecf6c238e04ce9e5a Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 29 Dec 2015 20:19:58 -0800 Subject: [PATCH 096/124] Fix #1062 --- release-notes/VERSION | 1 + .../databind/jsonFormatVisitors/JsonFormatTypes.java | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/release-notes/VERSION b/release-notes/VERSION index 2b5d28dd8f..b6429f4cc2 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -56,6 +56,7 @@ Project: jackson-databind (reported by fabiolaa@github) #1044: Add `AnnotationIntrospector.resolveSetterConflict(...)` to allow custom setter conflict resolution (suggested by clydebarrow@github) +#1062: Add new `JsonFormatTypes` enumerated value, `UNION` - Make `JsonValueFormat` (self-)serializable, deserializable, to/from valid external value (as per JSON Schema spec) diff --git a/src/main/java/com/fasterxml/jackson/databind/jsonFormatVisitors/JsonFormatTypes.java b/src/main/java/com/fasterxml/jackson/databind/jsonFormatVisitors/JsonFormatTypes.java index f1d4cb21af..538354a0df 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsonFormatVisitors/JsonFormatTypes.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsonFormatVisitors/JsonFormatTypes.java @@ -14,6 +14,14 @@ public enum JsonFormatTypes OBJECT, ARRAY, NULL, + + /** + * Virtual type used for union types; needed to make JSON Schema work with union types. + * + * @since 2.7 + */ + UNION, + ANY; private static final Map _byLCName = new HashMap(); From ee3e2ea8962730b2febcaca8ec39a9820b34505a Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 29 Dec 2015 20:21:58 -0800 Subject: [PATCH 097/124] Undo #1062. --- release-notes/VERSION | 1 - .../databind/jsonFormatVisitors/JsonFormatTypes.java | 8 -------- 2 files changed, 9 deletions(-) diff --git a/release-notes/VERSION b/release-notes/VERSION index b6429f4cc2..2b5d28dd8f 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -56,7 +56,6 @@ Project: jackson-databind (reported by fabiolaa@github) #1044: Add `AnnotationIntrospector.resolveSetterConflict(...)` to allow custom setter conflict resolution (suggested by clydebarrow@github) -#1062: Add new `JsonFormatTypes` enumerated value, `UNION` - Make `JsonValueFormat` (self-)serializable, deserializable, to/from valid external value (as per JSON Schema spec) diff --git a/src/main/java/com/fasterxml/jackson/databind/jsonFormatVisitors/JsonFormatTypes.java b/src/main/java/com/fasterxml/jackson/databind/jsonFormatVisitors/JsonFormatTypes.java index 538354a0df..f1d4cb21af 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsonFormatVisitors/JsonFormatTypes.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsonFormatVisitors/JsonFormatTypes.java @@ -14,14 +14,6 @@ public enum JsonFormatTypes OBJECT, ARRAY, NULL, - - /** - * Virtual type used for union types; needed to make JSON Schema work with union types. - * - * @since 2.7 - */ - UNION, - ANY; private static final Map _byLCName = new HashMap(); From d6b35117fb055315d8777a13b78ab6aaa2584b9f Mon Sep 17 00:00:00 2001 From: Kirill Vlasov Date: Mon, 28 Dec 2015 16:50:59 +0500 Subject: [PATCH 098/124] Fixing squid:S1871 - Two branches in the same conditional structure should not have exactly the same implementation --- .../jackson/databind/deser/std/JsonNodeDeserializer.java | 3 +-- .../databind/jsontype/impl/AsPropertyTypeDeserializer.java | 4 +--- .../fasterxml/jackson/databind/ser/std/NumberSerializer.java | 4 +--- .../jackson/databind/TestObjectMapperBeanSerializer.java | 5 +---- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java index 35a76ce626..fdbb487509 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/std/JsonNodeDeserializer.java @@ -305,11 +305,10 @@ protected final JsonNode deserializeAny(JsonParser p, DeserializationContext ctx switch (p.getCurrentTokenId()) { case JsonTokenId.ID_START_OBJECT: case JsonTokenId.ID_END_OBJECT: // for empty JSON Objects we may point to this + case JsonTokenId.ID_FIELD_NAME: return deserializeObject(p, ctxt, nodeFactory); case JsonTokenId.ID_START_ARRAY: return deserializeArray(p, ctxt, nodeFactory); - case JsonTokenId.ID_FIELD_NAME: - return deserializeObject(p, ctxt, nodeFactory); case JsonTokenId.ID_EMBEDDED_OBJECT: return _fromEmbedded(p, ctxt, nodeFactory); case JsonTokenId.ID_STRING: diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java index 3cd4dc5ab3..50a712a2c1 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java @@ -71,7 +71,7 @@ public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ct JsonToken t = p.getCurrentToken(); if (t == JsonToken.START_OBJECT) { t = p.nextToken(); - } else if (t == JsonToken.START_ARRAY) { + } else if (t == JsonToken.START_ARRAY || t != JsonToken.FIELD_NAME) { /* This is most likely due to the fact that not all Java types are * serialized as JSON Objects; so if "as-property" inclusion is requested, * serialization of things like Lists must be instead handled as if @@ -80,8 +80,6 @@ public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ct * is defined, it will be asked to handle this case. */ return _deserializeTypedUsingDefaultImpl(p, ctxt, null); - } else if (t != JsonToken.FIELD_NAME) { - return _deserializeTypedUsingDefaultImpl(p, ctxt, null); } // Ok, let's try to find the property. But first, need token buffer... TokenBuffer tb = null; diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java index 13118a0ee2..ce640b8d02 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/NumberSerializer.java @@ -49,15 +49,13 @@ public void serialize(Number value, JsonGenerator g, SerializerProvider provider /* These shouldn't match (as there are more specific ones), * but just to be sure: */ - } else if (value instanceof Integer) { - g.writeNumber(value.intValue()); } else if (value instanceof Long) { g.writeNumber(value.longValue()); } else if (value instanceof Double) { g.writeNumber(value.doubleValue()); } else if (value instanceof Float) { g.writeNumber(value.floatValue()); - } else if ((value instanceof Byte) || (value instanceof Short)) { + } else if (value instanceof Integer || value instanceof Byte || value instanceof Short) { g.writeNumber(value.intValue()); // doesn't need to be cast to smaller numbers } else { // We'll have to use fallback "untyped" number write method diff --git a/src/test/java/com/fasterxml/jackson/databind/TestObjectMapperBeanSerializer.java b/src/test/java/com/fasterxml/jackson/databind/TestObjectMapperBeanSerializer.java index fa0d802458..11deefe0b9 100644 --- a/src/test/java/com/fasterxml/jackson/databind/TestObjectMapperBeanSerializer.java +++ b/src/test/java/com/fasterxml/jackson/databind/TestObjectMapperBeanSerializer.java @@ -39,10 +39,7 @@ public void testComplexObject() String name = jp.getCurrentName(); JsonToken t = jp.nextToken(); - if (name.equals("uri")) { - assertToken(JsonToken.VALUE_STRING, t); - assertEquals(FixtureObjectBase.VALUE_URSTR, getAndVerifyText(jp)); - } else if (name.equals("url")) { + if (name.equals("uri") || name.equals("url")) { assertToken(JsonToken.VALUE_STRING, t); assertEquals(FixtureObjectBase.VALUE_URSTR, getAndVerifyText(jp)); } else if (name.equals("testNull")) { From 2126b41a46dd014816fa1d0a2acaa87baa93810f Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 3 Jan 2016 15:54:24 -0800 Subject: [PATCH 099/124] clean up; add `@JsonTypeInfo` as indicator of "implied" property --- .../JacksonAnnotationIntrospector.java | 2 + .../impl/AsWrapperTypeSerializer.java | 147 +++++++++--------- .../jackson/databind/util/TokenBuffer.java | 2 +- 3 files changed, 78 insertions(+), 73 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index 0e55b675b2..03659721bb 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -38,6 +38,7 @@ public class JacksonAnnotationIntrospector JsonSerialize.class, JsonView.class, JsonFormat.class, + JsonTypeInfo.class, JsonRawValue.class, JsonUnwrapped.class, JsonBackReference.class, @@ -50,6 +51,7 @@ public class JacksonAnnotationIntrospector JsonDeserialize.class, JsonView.class, JsonFormat.class, + JsonTypeInfo.class, JsonUnwrapped.class, JsonBackReference.class, JsonManagedReference.class diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsWrapperTypeSerializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsWrapperTypeSerializer.java index 3854b05e95..647d031be4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsWrapperTypeSerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsWrapperTypeSerializer.java @@ -31,134 +31,134 @@ public AsWrapperTypeSerializer forProperty(BeanProperty prop) { public As getTypeInclusion() { return As.WRAPPER_OBJECT; } @Override - public void writeTypePrefixForObject(Object value, JsonGenerator jgen) throws IOException + public void writeTypePrefixForObject(Object value, JsonGenerator g) throws IOException { String typeId = idFromValue(value); - if (jgen.canWriteTypeId()) { + if (g.canWriteTypeId()) { if (typeId != null) { - jgen.writeTypeId(typeId); + g.writeTypeId(typeId); } - jgen.writeStartObject(); + g.writeStartObject(); } else { // wrapper - jgen.writeStartObject(); + g.writeStartObject(); // and then JSON Object start caller wants // 28-Jan-2015, tatu: No really good answer here; can not really change // structure, so change null to empty String... - jgen.writeObjectFieldStart(_validTypeId(typeId)); + g.writeObjectFieldStart(_validTypeId(typeId)); } } @Override - public void writeTypePrefixForObject(Object value, JsonGenerator jgen, Class type) throws IOException + public void writeTypePrefixForObject(Object value, JsonGenerator g, Class type) throws IOException { String typeId = idFromValueAndType(value, type); - if (jgen.canWriteTypeId()) { + if (g.canWriteTypeId()) { if (typeId != null) { - jgen.writeTypeId(typeId); + g.writeTypeId(typeId); } - jgen.writeStartObject(); + g.writeStartObject(); } else { // wrapper - jgen.writeStartObject(); + g.writeStartObject(); // and then JSON Object start caller wants // 28-Jan-2015, tatu: No really good answer here; can not really change // structure, so change null to empty String... - jgen.writeObjectFieldStart(_validTypeId(typeId)); + g.writeObjectFieldStart(_validTypeId(typeId)); } } @Override - public void writeTypePrefixForArray(Object value, JsonGenerator jgen) throws IOException + public void writeTypePrefixForArray(Object value, JsonGenerator g) throws IOException { String typeId = idFromValue(value); - if (jgen.canWriteTypeId()) { + if (g.canWriteTypeId()) { if (typeId != null) { - jgen.writeTypeId(typeId); + g.writeTypeId(typeId); } - jgen.writeStartArray(); + g.writeStartArray(); } else { // can still wrap ok - jgen.writeStartObject(); - jgen.writeArrayFieldStart(_validTypeId(typeId)); + g.writeStartObject(); + g.writeArrayFieldStart(_validTypeId(typeId)); } } @Override - public void writeTypePrefixForArray(Object value, JsonGenerator jgen, Class type) throws IOException + public void writeTypePrefixForArray(Object value, JsonGenerator g, Class type) throws IOException { final String typeId = idFromValueAndType(value, type); - if (jgen.canWriteTypeId()) { + if (g.canWriteTypeId()) { if (typeId != null) { - jgen.writeTypeId(typeId); + g.writeTypeId(typeId); } - jgen.writeStartArray(); + g.writeStartArray(); } else { // can still wrap ok - jgen.writeStartObject(); + g.writeStartObject(); // and then JSON Array start caller wants - jgen.writeArrayFieldStart(_validTypeId(typeId)); + g.writeArrayFieldStart(_validTypeId(typeId)); } } @Override - public void writeTypePrefixForScalar(Object value, JsonGenerator jgen) throws IOException { + public void writeTypePrefixForScalar(Object value, JsonGenerator g) throws IOException { final String typeId = idFromValue(value); - if (jgen.canWriteTypeId()) { + if (g.canWriteTypeId()) { if (typeId != null) { - jgen.writeTypeId(typeId); + g.writeTypeId(typeId); } } else { // can still wrap ok - jgen.writeStartObject(); - jgen.writeFieldName(_validTypeId(typeId)); + g.writeStartObject(); + g.writeFieldName(_validTypeId(typeId)); } } @Override - public void writeTypePrefixForScalar(Object value, JsonGenerator jgen, Class type) throws IOException + public void writeTypePrefixForScalar(Object value, JsonGenerator g, Class type) throws IOException { final String typeId = idFromValueAndType(value, type); - if (jgen.canWriteTypeId()) { + if (g.canWriteTypeId()) { if (typeId != null) { - jgen.writeTypeId(typeId); + g.writeTypeId(typeId); } } else { // can still wrap ok - jgen.writeStartObject(); - jgen.writeFieldName(_validTypeId(typeId)); + g.writeStartObject(); + g.writeFieldName(_validTypeId(typeId)); } } @Override - public void writeTypeSuffixForObject(Object value, JsonGenerator jgen) throws IOException + public void writeTypeSuffixForObject(Object value, JsonGenerator g) throws IOException { // first close JSON Object caller used - jgen.writeEndObject(); - if (!jgen.canWriteTypeId()) { + g.writeEndObject(); + if (!g.canWriteTypeId()) { // and then wrapper - jgen.writeEndObject(); + g.writeEndObject(); } } @Override - public void writeTypeSuffixForArray(Object value, JsonGenerator jgen) throws IOException + public void writeTypeSuffixForArray(Object value, JsonGenerator g) throws IOException { // first close array caller needed - jgen.writeEndArray(); - if (!jgen.canWriteTypeId()) { + g.writeEndArray(); + if (!g.canWriteTypeId()) { // then wrapper object - jgen.writeEndObject(); + g.writeEndObject(); } } @Override - public void writeTypeSuffixForScalar(Object value, JsonGenerator jgen) throws IOException { - if (!jgen.canWriteTypeId()) { + public void writeTypeSuffixForScalar(Object value, JsonGenerator g) throws IOException { + if (!g.canWriteTypeId()) { // just need to close the wrapper object - jgen.writeEndObject(); + g.writeEndObject(); } } @@ -169,61 +169,61 @@ public void writeTypeSuffixForScalar(Object value, JsonGenerator jgen) throws IO */ @Override - public void writeCustomTypePrefixForObject(Object value, JsonGenerator jgen, String typeId) throws IOException { - if (jgen.canWriteTypeId()) { + public void writeCustomTypePrefixForObject(Object value, JsonGenerator g, String typeId) throws IOException { + if (g.canWriteTypeId()) { if (typeId != null) { - jgen.writeTypeId(typeId); + g.writeTypeId(typeId); } - jgen.writeStartObject(); + g.writeStartObject(); } else { - jgen.writeStartObject(); - jgen.writeObjectFieldStart(_validTypeId(typeId)); + g.writeStartObject(); + g.writeObjectFieldStart(_validTypeId(typeId)); } } @Override - public void writeCustomTypePrefixForArray(Object value, JsonGenerator jgen, String typeId) throws IOException { - if (jgen.canWriteTypeId()) { + public void writeCustomTypePrefixForArray(Object value, JsonGenerator g, String typeId) throws IOException { + if (g.canWriteTypeId()) { if (typeId != null) { - jgen.writeTypeId(typeId); + g.writeTypeId(typeId); } - jgen.writeStartArray(); + g.writeStartArray(); } else { - jgen.writeStartObject(); - jgen.writeArrayFieldStart(_validTypeId(typeId)); + g.writeStartObject(); + g.writeArrayFieldStart(_validTypeId(typeId)); } } @Override - public void writeCustomTypePrefixForScalar(Object value, JsonGenerator jgen, String typeId) throws IOException { - if (jgen.canWriteTypeId()) { + public void writeCustomTypePrefixForScalar(Object value, JsonGenerator g, String typeId) throws IOException { + if (g.canWriteTypeId()) { if (typeId != null) { - jgen.writeTypeId(typeId); + g.writeTypeId(typeId); } } else { - jgen.writeStartObject(); - jgen.writeFieldName(_validTypeId(typeId)); + g.writeStartObject(); + g.writeFieldName(_validTypeId(typeId)); } } @Override - public void writeCustomTypeSuffixForObject(Object value, JsonGenerator jgen, String typeId) throws IOException { - if (!jgen.canWriteTypeId()) { - writeTypeSuffixForObject(value, jgen); // standard impl works fine + public void writeCustomTypeSuffixForObject(Object value, JsonGenerator g, String typeId) throws IOException { + if (!g.canWriteTypeId()) { + writeTypeSuffixForObject(value, g); // standard impl works fine } } @Override - public void writeCustomTypeSuffixForArray(Object value, JsonGenerator jgen, String typeId) throws IOException { - if (!jgen.canWriteTypeId()) { - writeTypeSuffixForArray(value, jgen); // standard impl works fine + public void writeCustomTypeSuffixForArray(Object value, JsonGenerator g, String typeId) throws IOException { + if (!g.canWriteTypeId()) { + writeTypeSuffixForArray(value, g); // standard impl works fine } } @Override - public void writeCustomTypeSuffixForScalar(Object value, JsonGenerator jgen, String typeId) throws IOException { - if (!jgen.canWriteTypeId()) { - writeTypeSuffixForScalar(value, jgen); // standard impl works fine + public void writeCustomTypeSuffixForScalar(Object value, JsonGenerator g, String typeId) throws IOException { + if (!g.canWriteTypeId()) { + writeTypeSuffixForScalar(value, g); // standard impl works fine } } @@ -234,7 +234,10 @@ public void writeCustomTypeSuffixForScalar(Object value, JsonGenerator jgen, Str */ /** - * @since 2.6.0 + * Helper method used to ensure that intended type id is output as something that is valid: + * currently only used to ensure that `null` output is converted to an empty String. + * + * @since 2.6 */ protected String _validTypeId(String typeId) { return (typeId == null) ? "" : typeId; diff --git a/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java b/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java index 413f3ab10b..0c98e87774 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java @@ -114,7 +114,7 @@ public class TokenBuffer protected Object _objectId; /** - * Do we currnetly have a native type or object id buffered? + * Do we currently have a native type or object id buffered? */ protected boolean _hasNativeId = false; From b11e5ff57170102ae39252daaac425d1d2ebbd62 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Tue, 5 Jan 2016 22:21:51 -0800 Subject: [PATCH 100/124] prepare for 2.7.0-rc3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index bfc062f5f3..70d94ba910 100644 --- a/pom.xml +++ b/pom.xml @@ -48,12 +48,12 @@ com.fasterxml.jackson.core jackson-annotations - 2.7.0-rc3-SNAPSHOT + 2.7.0-rc3 com.fasterxml.jackson.core jackson-core - 2.7.0-rc3-SNAPSHOT + 2.7.0-rc3 - 2.7.0-rc3 com.fasterxml.jackson.core jackson-core - 2.7.0-rc4-SNAPSHOT + 2.7.0