diff options
author | Tatu Saloranta <tatu.saloranta@iki.fi> | 2015-06-19 09:33:30 -0700 |
---|---|---|
committer | Tatu Saloranta <tatu.saloranta@iki.fi> | 2015-06-19 09:33:30 -0700 |
commit | bd256eaa8934384409ac74ec58bf67925209af35 (patch) | |
tree | 3c86c0444badaf25290d164825211ac4d06ceab4 | |
parent | 7defcfe28b4d9965a63215e8ec6f1b2b0f339cda (diff) | |
download | jackson-databind-bd256eaa8934384409ac74ec58bf67925209af35.tar.gz |
More work on contextual property features via @JsonFormat
6 files changed, 211 insertions, 73 deletions
diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringArraySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringArraySerializer.java index bb0c2045c..41916bdb9 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringArraySerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/StringArraySerializer.java @@ -56,7 +56,12 @@ public class StringArraySerializer super(src, prop, unwrapSingle); _elementSerializer = (JsonSerializer<Object>) ser; } - + + @Override + public JsonSerializer<?> _withResolved(BeanProperty prop, Boolean unwrapSingle) { + return new StringArraySerializer(this, prop, _elementSerializer, unwrapSingle); + } + /** * Strings never add type info; hence, even if type serializer is suggested, * we'll ignore it... diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ArraySerializerBase.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ArraySerializerBase.java index 3df6d3593..9ec87fd48 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/ArraySerializerBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ArraySerializerBase.java @@ -2,6 +2,7 @@ package com.fasterxml.jackson.databind.ser.std; import java.io.IOException; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; @@ -16,6 +17,7 @@ import com.fasterxml.jackson.databind.ser.*; @SuppressWarnings("serial") public abstract class ArraySerializerBase<T> extends ContainerSerializer<T> + implements ContextualSerializer // for 'unwrapSingleElemArray' { protected final BeanProperty _property; @@ -78,16 +80,45 @@ public abstract class ArraySerializerBase<T> _unwrapSingle = src._unwrapSingle; } + /** + * @since 2.6 + */ + public abstract JsonSerializer<?> _withResolved(BeanProperty prop, + Boolean unwrapSingle); + + @Override + public JsonSerializer<?> createContextual(SerializerProvider provider, + BeanProperty property) throws JsonMappingException + { + Boolean unwrapSingle = null; + + // First: if we have a property, may have property-annotation overrides + if (property != null) { + final AnnotationIntrospector intr = provider.getAnnotationIntrospector(); + JsonFormat.Value format = property.findFormatOverrides(intr); + if (format != null) { + unwrapSingle = format.getFeature(JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED); + if (unwrapSingle != _unwrapSingle) { + return _withResolved(property, unwrapSingle); + } + } + } + return this; + } + // NOTE: as of 2.5, sub-classes SHOULD override (in 2.4 and before, was final), // at least if they can provide access to actual size of value and use `writeStartArray()` // variant that passes size of array to output, which is helpful with some data formats @Override public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException { - if (provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED) - && hasSingleElement(value)) { - serializeContents(value, gen, provider); - return; + if (((_unwrapSingle == null) && + provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)) + || (_unwrapSingle == Boolean.TRUE)) { + if (hasSingleElement(value)) { + serializeContents(value, gen, provider); + return; + } } gen.writeStartArray(); // [databind#631]: Assign current value, to be accessible by custom serializers diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ByteArraySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ByteArraySerializer.java new file mode 100644 index 000000000..536dc6b97 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ByteArraySerializer.java @@ -0,0 +1,80 @@ +package com.fasterxml.jackson.databind.ser.std; + +import java.io.IOException; +import java.lang.reflect.Type; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor; +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes; +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Unlike other integral number array serializers, we do not just print out byte values + * as numbers. Instead, we assume that it would make more sense to output content + * as base64 encoded bytes (using default base64 encoding). + *<p> + * NOTE: since it is NOT serialized as an array, can not use AsArraySerializer as base + *<p> + * NOTE: since 2.6, has been a main-level class; earlier was embedded in + * {@link StdArraySerializers}. + */ +@JacksonStdImpl +public class ByteArraySerializer extends StdSerializer<byte[]> +{ + private static final long serialVersionUID = 1L; + + public ByteArraySerializer() { + super(byte[].class); + } + + @Override + public boolean isEmpty(SerializerProvider prov, byte[] value) { + return (value == null) || (value.length == 0); + } + + @Override + public void serialize(byte[] value, JsonGenerator jgen, SerializerProvider provider) + throws IOException + { + jgen.writeBinary(provider.getConfig().getBase64Variant(), + value, 0, value.length); + } + + @Override + public void serializeWithType(byte[] value, JsonGenerator jgen, SerializerProvider provider, + TypeSerializer typeSer) + throws IOException + { + typeSer.writeTypePrefixForScalar(value, jgen); + jgen.writeBinary(provider.getConfig().getBase64Variant(), + value, 0, value.length); + typeSer.writeTypeSuffixForScalar(value, jgen); + } + + @Override + public JsonNode getSchema(SerializerProvider provider, Type typeHint) + { + ObjectNode o = createSchemaNode("array", true); + ObjectNode itemSchema = createSchemaNode("string"); //binary values written as strings? + return o.set("items", itemSchema); + } + + @Override + public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) + throws JsonMappingException + { + if (visitor != null) { + JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint); + if (v2 != null) { + v2.itemsFormat(JsonFormatTypes.STRING); + } + } + } +}
\ No newline at end of file diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/ObjectArraySerializer.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/ObjectArraySerializer.java index 21f76476a..0fbe9148e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/ObjectArraySerializer.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/ObjectArraySerializer.java @@ -95,10 +95,15 @@ public class ObjectArraySerializer _dynamicSerializers = src._dynamicSerializers; _elementSerializer = (JsonSerializer<Object>) elementSerializer; } + + @Override + public JsonSerializer<?> _withResolved(BeanProperty prop, Boolean unwrapSingle) { + return new ObjectArraySerializer(this, prop, + _valueTypeSerializer, _elementSerializer, unwrapSingle); + } @Override - public ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) - { + public ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) { return new ObjectArraySerializer(_elementType, _staticTyping, vts, _elementSerializer); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdArraySerializers.java b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdArraySerializers.java index 733edc6e5..5f753505e 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/std/StdArraySerializers.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/std/StdArraySerializers.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.lang.reflect.Type; import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; @@ -13,6 +14,7 @@ import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; 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.type.TypeFactory; /** @@ -27,7 +29,7 @@ public class StdArraySerializers static { // Arrays of various types (including common object types) _arraySerializers.put(boolean[].class.getName(), new StdArraySerializers.BooleanArraySerializer()); - _arraySerializers.put(byte[].class.getName(), new StdArraySerializers.ByteArraySerializer()); + _arraySerializers.put(byte[].class.getName(), new ByteArraySerializer()); _arraySerializers.put(char[].class.getName(), new StdArraySerializers.CharArraySerializer()); _arraySerializers.put(short[].class.getName(), new StdArraySerializers.ShortArraySerializer()); _arraySerializers.put(int[].class.getName(), new StdArraySerializers.IntArraySerializer()); @@ -56,7 +58,8 @@ public class StdArraySerializers * Intermediate base class used for cases where we may add * type information (excludes boolean/int/double arrays). */ - protected abstract static class TypedPrimitiveArraySerializer<T> extends ArraySerializerBase<T> + protected abstract static class TypedPrimitiveArraySerializer<T> + extends ArraySerializerBase<T> { /** * Type serializer to use for values, if any. @@ -82,13 +85,24 @@ public class StdArraySerializers */ @JacksonStdImpl - public static class BooleanArraySerializer extends ArraySerializerBase<boolean[]> + public static class BooleanArraySerializer + extends ArraySerializerBase<boolean[]> { // as above, assuming no one re-defines primitive/wrapper types private final static JavaType VALUE_TYPE = TypeFactory.defaultInstance().uncheckedSimpleType(Boolean.class); public BooleanArraySerializer() { super(boolean[].class); } + protected BooleanArraySerializer(BooleanArraySerializer src, + BeanProperty prop, Boolean unwrapSingle) { + super(src, prop, unwrapSingle); + } + + @Override + public JsonSerializer<?> _withResolved(BeanProperty prop, Boolean unwrapSingle) { + return new BooleanArraySerializer(this, prop, unwrapSingle); + } + /** * Booleans never add type info; hence, even if type serializer is suggested, * we'll ignore it... @@ -167,62 +181,14 @@ public class StdArraySerializers } /** - * Unlike other integral number array serializers, we do not just print out byte values - * as numbers. Instead, we assume that it would make more sense to output content - * as base64 encoded bytes (using default base64 encoding). - *<p> - * NOTE: since it is NOT serialized as an array, can not use AsArraySerializer as base + * @deprecated Since 2.6 use the main-level implementation, base class of this class */ + @Deprecated @JacksonStdImpl - public static class ByteArraySerializer extends StdSerializer<byte[]> + public static class ByteArraySerializer + extends com.fasterxml.jackson.databind.ser.std.ByteArraySerializer { - public ByteArraySerializer() { - super(byte[].class); - } - - @Override - public boolean isEmpty(SerializerProvider prov, byte[] value) { - return (value == null) || (value.length == 0); - } - - @Override - public void serialize(byte[] value, JsonGenerator jgen, SerializerProvider provider) - throws IOException - { - jgen.writeBinary(provider.getConfig().getBase64Variant(), - value, 0, value.length); - } - - @Override - public void serializeWithType(byte[] value, JsonGenerator jgen, SerializerProvider provider, - TypeSerializer typeSer) - throws IOException - { - typeSer.writeTypePrefixForScalar(value, jgen); - jgen.writeBinary(provider.getConfig().getBase64Variant(), - value, 0, value.length); - typeSer.writeTypeSuffixForScalar(value, jgen); - } - - @Override - public JsonNode getSchema(SerializerProvider provider, Type typeHint) - { - ObjectNode o = createSchemaNode("array", true); - ObjectNode itemSchema = createSchemaNode("string"); //binary values written as strings? - return o.set("items", itemSchema); - } - - @Override - public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) - throws JsonMappingException - { - if (visitor != null) { - JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint); - if (v2 != null) { - v2.itemsFormat(JsonFormatTypes.STRING); - } - } - } + public ByteArraySerializer() { super(); } } @JacksonStdImpl @@ -238,6 +204,11 @@ public class StdArraySerializers } @Override + public JsonSerializer<?> _withResolved(BeanProperty prop,Boolean unwrapSingle) { + return new ShortArraySerializer(this, prop, _valueTypeSerializer, unwrapSingle); + } + + @Override public ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) { return new ShortArraySerializer(this, _property, vts, _unwrapSingle); } @@ -406,6 +377,19 @@ public class StdArraySerializers public IntArraySerializer() { super(int[].class); } /** + * @since 2.6 + */ + protected IntArraySerializer(IntArraySerializer src, + BeanProperty prop, Boolean unwrapSingle) { + super(src, prop, unwrapSingle); + } + + @Override + public JsonSerializer<?> _withResolved(BeanProperty prop, Boolean unwrapSingle) { + return new IntArraySerializer(this, prop, unwrapSingle); + } + + /** * Ints never add type info; hence, even if type serializer is suggested, * we'll ignore it... */ @@ -491,6 +475,11 @@ public class StdArraySerializers } @Override + public JsonSerializer<?> _withResolved(BeanProperty prop,Boolean unwrapSingle) { + return new LongArraySerializer(this, prop, _valueTypeSerializer, unwrapSingle); + } + + @Override public ContainerSerializer<?> _withValueTypeSerializer(TypeSerializer vts) { return new LongArraySerializer(this, _property, vts, _unwrapSingle); } @@ -591,6 +580,11 @@ public class StdArraySerializers } @Override + public JsonSerializer<?> _withResolved(BeanProperty prop,Boolean unwrapSingle) { + return new FloatArraySerializer(this, prop, _valueTypeSerializer, unwrapSingle); + } + + @Override public JavaType getContentType() { return VALUE_TYPE; } @@ -671,6 +665,19 @@ public class StdArraySerializers public DoubleArraySerializer() { super(double[].class); } /** + * @since 2.6 + */ + protected DoubleArraySerializer(DoubleArraySerializer src, + BeanProperty prop, Boolean unwrapSingle) { + super(src, prop, unwrapSingle); + } + + @Override + public JsonSerializer<?> _withResolved(BeanProperty prop, Boolean unwrapSingle) { + return new DoubleArraySerializer(this, prop, unwrapSingle); + } + + /** * Doubles never add type info; hence, even if type serializer is suggested, * we'll ignore it... */ diff --git a/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeaturesTest.java b/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeaturesTest.java index deb1276b6..602ddcd59 100644 --- a/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeaturesTest.java +++ b/src/test/java/com/fasterxml/jackson/databind/struct/FormatFeaturesTest.java @@ -1,5 +1,7 @@ package com.fasterxml.jackson.databind.struct; +import java.util.*; + import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.databind.*; @@ -9,7 +11,7 @@ import com.fasterxml.jackson.databind.*; public class FormatFeaturesTest extends BaseMapTest { @JsonPropertyOrder( { "strings", "ints", "bools" }) - static class WrapWriteTest + static class WrapWriteWithArrays { @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED }) public String[] strings = new String[] { @@ -24,26 +26,34 @@ public class FormatFeaturesTest extends BaseMapTest public boolean[] bools = new boolean[] { true }; } + @JsonPropertyOrder( { "strings", "ints", "bools" }) + static class WrapWriteWithCollections + { + @JsonFormat(with={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED }) + public List<String> strings = Arrays.asList("a"); + + @JsonFormat(without={ JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED }) + public Collection<Integer> ints = Arrays.asList(Integer.valueOf(1)); + + public Set<Boolean> bools = Collections.singleton(true); + } + private final ObjectMapper MAPPER = new ObjectMapper(); public void testWriteSingleElemArrayUnwrapped() throws Exception { - // Comment out temporarily, to prevent build fail! - - /* - // default: strings unwrapped, ints wrapped assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':[true]}"), - MAPPER.writeValueAsString(new WrapWriteTest())); + MAPPER.writeValueAsString(new WrapWriteWithArrays())); // change global default to "yes, unwrap"; changes 'bools' only assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':true}"), - MAPPER.writeValueAsString(new WrapWriteTest())); + MAPPER.writer().with(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED) + .writeValueAsString(new WrapWriteWithArrays())); // change global default to "no, don't, unwrap", same as first case assertEquals(aposToQuotes("{'strings':'a','ints':[1],'bools':[true]}"), - MAPPER.writeValueAsString(new WrapWriteTest())); - */ + MAPPER.writer().without(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED) + .writeValueAsString(new WrapWriteWithArrays())); } - } |