From 73a87db9650dd7e8db57727b7598c68e7c46e6ff Mon Sep 17 00:00:00 2001 From: Cowtowncoder Date: Mon, 8 Dec 2014 17:03:28 -0800 Subject: [PATCH] Complete #638 implementation --- release-notes/VERSION | 2 + .../databind/ser/BeanPropertyWriter.java | 40 ++++++++++-- .../ser/VirtualBeanPropertyWriter.java | 32 +++++++--- .../databind/ser/TestVirtualProperties.java | 61 +++++++++++++++++-- 4 files changed, 116 insertions(+), 19 deletions(-) diff --git a/release-notes/VERSION b/release-notes/VERSION index 2c0f786b86..882a1dd17b 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -51,6 +51,8 @@ Project: jackson-databind #634: Add `typeFromId(DatabindContext,String)` in `TypeIdDeserializer` #636: `ClassNotFoundException` for classes not (yet) needed during serialization (contributed by mspiegel@github) +#638: Add annotation-based method(s) for injecting properties during serialization + (using @JsonAppend, VirtualBeanPropertyWriter) - Allow use of `Shape.ARRAY` for Enums, as an alias to 'use index' - Start using `JsonGenerator.writeStartArray(int)` to help data formats that benefit from knowing number of elements in arrays (and would otherwise 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 caeebf2797..18d17d7675 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java @@ -194,14 +194,17 @@ public BeanPropertyWriter(BeanPropertyDefinition propDef, { _member = member; _contextAnnotations = contextAnnotations; + _name = new SerializedString(propDef.getName()); _wrapperName = propDef.getWrapperName(); + _metadata = propDef.getMetadata(); + _includeInViews = propDef.findViews(); + _declaredType = declaredType; _serializer = (JsonSerializer) ser; _dynamicSerializers = (ser == null) ? PropertySerializerMap.emptyMap() : null; _typeSerializer = typeSer; _cfgSerializationType = serType; - _metadata = propDef.getMetadata(); if (member instanceof AnnotatedField) { _accessorMethod = null; @@ -210,18 +213,47 @@ public BeanPropertyWriter(BeanPropertyDefinition propDef, _accessorMethod = (Method) member.getMember(); _field = null; } else { - // 01-Dec-2014, tatu: Used to be illegal, but now explicitly allowed + // 01-Dec-2014, tatu: Used to be illegal, but now explicitly allowed for virtual props _accessorMethod = null; _field = null; } _suppressNulls = suppressNulls; _suppressableValue = suppressableValue; - _includeInViews = propDef.findViews(); // this will be resolved later on, unless nulls are to be suppressed _nullSerializer = null; } - + + /** + * Constructor that may be of use to virtual properties, when there is need for + * the zero-arg ("default") constructor, and actual initialization is done + * after constructor call. + * + * @since 2.5 + */ + protected BeanPropertyWriter() { + _member = null; + _contextAnnotations = null; + + _name = null; + _wrapperName = null; + _metadata = null; + _includeInViews = null; + + _declaredType = null; + _serializer = null; + _dynamicSerializers = null; + _typeSerializer = null; + _cfgSerializationType = null; + + _accessorMethod = null; + _field = null; + _suppressNulls = false; + _suppressableValue = null; + + _nullSerializer = null; + } + /** * "Copy constructor" to be used by filtering sub-classes */ diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/VirtualBeanPropertyWriter.java b/src/main/java/com/fasterxml/jackson/databind/ser/VirtualBeanPropertyWriter.java index 9df2620e02..fb6aea645d 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/VirtualBeanPropertyWriter.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/VirtualBeanPropertyWriter.java @@ -17,17 +17,31 @@ * to add "virtual" properties in addition to regular ones. * * @since 2.5 + * + * @see com.fasterxml.jackson.databind.ser.impl.AttributePropertyWriter */ public abstract class VirtualBeanPropertyWriter extends BeanPropertyWriter { + /** + * Constructor used by most sub-types. + */ protected VirtualBeanPropertyWriter(BeanPropertyDefinition propDef, - Annotations contextAnnotations, JavaType declaredType, - JsonSerializer ser, TypeSerializer typeSer, JavaType serType) { - this(propDef, contextAnnotations, declaredType, ser, typeSer, serType, + Annotations contextAnnotations, JavaType declaredType) + { + this(propDef, contextAnnotations, declaredType, null, null, null, propDef.findInclusion()); } + /** + * Constructor that may be used by sub-classes for constructing a "blue-print" instance; + * one that will only become (or create) actual usable instance when its + * {@link #withConfig} method is called. + */ + protected VirtualBeanPropertyWriter() { + super(); + } + /** * Pass-through constructor that may be used by sub-classes that * want full control over implementation. @@ -61,7 +75,7 @@ protected static Object _suppressableValue(JsonInclude.Include inclusion) { } return null; } - + /* /********************************************************** /* Standard accessor overrides @@ -98,13 +112,13 @@ public Type getGenericPropertyType() { /** * Contextualization method called on a newly constructed virtual bean property. - * If information is used to change behavior, it is recommended that a new instance - * is constructed with given information and returned; however, this is not mandatory - * and modifying (and returning) this instance is legal as well as calls are made - * in thread-safe manner. + * Usually a new intance needs to be created due to finality of some of configuration + * members; otherwise while recommended, creating a new instance is not strictly-speaking + * mandatory because calls are made in thread-safe manner, as part of initialization + * before use. * * @param config Currenct configuration; guaranteed to be {@link SerializationConfig} - * (but not typed since caller does not have full typing) + * (just not typed since caller does not have dependency to serialization-specific types) * @param declaringClass Class that contains this property writer * @param propDef Nominal property definition to use * @param type Declared type for the property diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/TestVirtualProperties.java b/src/test/java/com/fasterxml/jackson/databind/ser/TestVirtualProperties.java index 969e0d9546..865c3c6b8a 100644 --- a/src/test/java/com/fasterxml/jackson/databind/ser/TestVirtualProperties.java +++ b/src/test/java/com/fasterxml/jackson/databind/ser/TestVirtualProperties.java @@ -3,10 +3,14 @@ import java.util.*; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.BaseMapTest; -import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonAppend; +import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.introspect.AnnotatedClass; +import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; +import com.fasterxml.jackson.databind.util.Annotations; /** * Tests for verifying that one can append virtual properties after regular ones. @@ -26,10 +30,10 @@ static class SimpleBean @JsonAppend(prepend=true, attrs={ @JsonAppend.Attr("id"), @JsonAppend.Attr(value="internal", propName="extra") }) - static class SimpleBeanPrepend - { - public int value = 13; - } + static class SimpleBeanPrepend + { + public int value = 13; + } enum ABC { A, B, C; @@ -40,6 +44,44 @@ static class OptionalsBean { public int value = 28; } + + static class CustomVProperty + extends VirtualBeanPropertyWriter + { + private CustomVProperty() { super(); } + + private CustomVProperty(BeanPropertyDefinition propDef, + Annotations ctxtAnn, JavaType type) { + super(propDef, ctxtAnn, type); + } + + @Override + protected Object value(Object bean, JsonGenerator jgen, SerializerProvider prov) { + if (_name.toString().equals("id")) { + return "abc123"; + } + if (_name.toString().equals("extra")) { + return new int[] { 42 }; + } + return "???"; + } + + @Override + public VirtualBeanPropertyWriter withConfig(MapperConfig config, + AnnotatedClass declaringClass, BeanPropertyDefinition propDef, + JavaType type) + { + return new CustomVProperty(propDef, declaringClass.getAnnotations(), type); + } + } + + @JsonAppend(prepend=true, props={ @JsonAppend.Prop(value=CustomVProperty.class, name="id"), + @JsonAppend.Prop(value=CustomVProperty.class, name="extra") + }) + static class CustomVBean + { + public int value = 72; + } /* /********************************************************** @@ -82,4 +124,11 @@ public void testAttributePropInclusion() throws Exception .writeValueAsString(new OptionalsBean()); assertEquals(aposToQuotes("{'value':28}"), json); } + + public void testCustomProperties() throws Exception + { + String json = WRITER.withAttribute("desc", "nice") + .writeValueAsString(new CustomVBean()); + assertEquals(aposToQuotes("{'id':'abc123','extra':[42],'value':72}"), json); + } }