diff options
author | Tatu Saloranta <tsaloranta@gmail.com> | 2012-01-22 16:03:47 -0800 |
---|---|---|
committer | Tatu Saloranta <tsaloranta@gmail.com> | 2012-01-22 16:03:47 -0800 |
commit | e1961745ea5209c66990f76ba94bf8cd02b77387 (patch) | |
tree | 5b16fe592c42377c23b6c26eefe0781f99fb28eb /src/main/java/com/fasterxml/jackson/databind | |
parent | 1a7c6f905711ecf704576024dfead79aee47b320 (diff) | |
download | jackson-databind-e1961745ea5209c66990f76ba94bf8cd02b77387.tar.gz |
Implement [JACKSON-764], programmatic way to set root name to use for root-wrapping
Diffstat (limited to 'src/main/java/com/fasterxml/jackson/databind')
8 files changed, 441 insertions, 315 deletions
diff --git a/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java b/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java index 9becbacea..90abbd183 100644 --- a/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java +++ b/src/main/java/com/fasterxml/jackson/databind/DeserializationConfig.java @@ -37,7 +37,7 @@ import com.fasterxml.jackson.databind.util.LinkedNode; * with respect to mix-in annotations; where this is guaranteed as * long as caller follow "copy-then-use" pattern) */ -public class DeserializationConfig +public final class DeserializationConfig extends MapperConfigBase<DeserializationConfig.Feature, DeserializationConfig> { /** @@ -334,7 +334,15 @@ public class DeserializationConfig _problemHandlers = problemHandlers; _nodeFactory = src._nodeFactory; } - + + private DeserializationConfig(DeserializationConfig src, String rootName) + { + super(src, rootName); + _deserFeatures = src._deserFeatures; + _problemHandlers = src._problemHandlers; + _nodeFactory = src._nodeFactory; + } + /* /********************************************************** /* Life-cycle, factory methods from MapperConfig @@ -365,62 +373,78 @@ public class DeserializationConfig @Override public DeserializationConfig withClassIntrospector(ClassIntrospector<? extends BeanDescription> ci) { - return new DeserializationConfig(this, _base.withClassIntrospector(ci)); + return _withBase(_base.withClassIntrospector(ci)); } @Override public DeserializationConfig withAnnotationIntrospector(AnnotationIntrospector ai) { - return new DeserializationConfig(this, _base.withAnnotationIntrospector(ai)); + return _withBase(_base.withAnnotationIntrospector(ai)); } @Override public DeserializationConfig withVisibilityChecker(VisibilityChecker<?> vc) { - return new DeserializationConfig(this, _base.withVisibilityChecker(vc)); + return _withBase(_base.withVisibilityChecker(vc)); } @Override public DeserializationConfig withVisibility(PropertyAccessor forMethod, JsonAutoDetect.Visibility visibility) { - return new DeserializationConfig(this, _base.withVisibility(forMethod, visibility)); + return _withBase( _base.withVisibility(forMethod, visibility)); } @Override public DeserializationConfig withTypeResolverBuilder(TypeResolverBuilder<?> trb) { - return new DeserializationConfig(this, _base.withTypeResolverBuilder(trb)); + return _withBase(_base.withTypeResolverBuilder(trb)); } @Override public DeserializationConfig withSubtypeResolver(SubtypeResolver str) { - return new DeserializationConfig(this, str); + return (_subtypeResolver == str) ? this : new DeserializationConfig(this, str); } @Override public DeserializationConfig withPropertyNamingStrategy(PropertyNamingStrategy pns) { - return new DeserializationConfig(this, _base.withPropertyNamingStrategy(pns)); + return _withBase(_base.withPropertyNamingStrategy(pns)); + } + + @Override + public DeserializationConfig withRootName(String rootName) { + if (rootName == null) { + if (_rootName == null) { + return this; + } + } else if (rootName.equals(_rootName)) { + return this; + } + return new DeserializationConfig(this, rootName); } @Override public DeserializationConfig withTypeFactory(TypeFactory tf) { - return (tf == _base.getTypeFactory()) ? this : new DeserializationConfig(this, _base.withTypeFactory(tf)); + return _withBase( _base.withTypeFactory(tf)); } @Override public DeserializationConfig withDateFormat(DateFormat df) { - return (df == _base.getDateFormat()) ? this : new DeserializationConfig(this, _base.withDateFormat(df)); + return _withBase(_base.withDateFormat(df)); } @Override public DeserializationConfig withHandlerInstantiator(HandlerInstantiator hi) { - return (hi == _base.getHandlerInstantiator()) ? this : new DeserializationConfig(this, _base.withHandlerInstantiator(hi)); + return _withBase(_base.withHandlerInstantiator(hi)); } @Override public DeserializationConfig withInsertedAnnotationIntrospector(AnnotationIntrospector ai) { - return new DeserializationConfig(this, _base.withInsertedAnnotationIntrospector(ai)); + return _withBase(_base.withInsertedAnnotationIntrospector(ai)); } @Override public DeserializationConfig withAppendedAnnotationIntrospector(AnnotationIntrospector ai) { - return new DeserializationConfig(this, _base.withAppendedAnnotationIntrospector(ai)); + return _withBase(_base.withAppendedAnnotationIntrospector(ai)); + } + + private final DeserializationConfig _withBase(BaseSettings newBase) { + return (_base == newBase) ? this : new DeserializationConfig(this, newBase); } /* @@ -544,6 +568,15 @@ public class DeserializationConfig } return NopAnnotationIntrospector.instance; } + + @Override + public boolean useRootWrapping() + { + if (_rootName != null) { // empty String disables wrapping; non-empty enables + return (_rootName.length() > 0); + } + return isEnabled(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE); + } /** * Accessor for getting bean description that only contains class @@ -582,7 +615,7 @@ public class DeserializationConfig return vchecker; } - public boolean isEnabled(DeserializationConfig.Feature f) { + public final boolean isEnabled(DeserializationConfig.Feature f) { return (_deserFeatures & f.getMask()) != 0; } diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java index 28dec9155..df86884b7 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java @@ -194,14 +194,12 @@ public class ObjectMapper protected final static VisibilityChecker<?> STD_VISIBILITY_CHECKER = VisibilityChecker.Std.defaultInstance(); /** - * This is the default {@link DateFormat} used unless overridden by - * custom implementation. + * Base settings contain defaults used for all {@link ObjectMapper} + * instances. */ - protected final static DateFormat DEFAULT_DATE_FORMAT = StdDateFormat.instance; - protected final static BaseSettings DEFAULT_BASE = new BaseSettings(DEFAULT_INTROSPECTOR, DEFAULT_ANNOTATION_INTROSPECTOR, STD_VISIBILITY_CHECKER, null, TypeFactory.defaultInstance(), - null, DEFAULT_DATE_FORMAT, null); + null, StdDateFormat.instance, null); /* /********************************************************** @@ -353,7 +351,7 @@ public class ObjectMapper } /** - * Construct mapper that uses specified {@link JsonFactory} + * Constructs instance that uses specified {@link JsonFactory} * for constructing necessary {@link JsonParser}s and/or * {@link JsonGenerator}s. */ @@ -362,25 +360,18 @@ public class ObjectMapper this(jf, null, null); } - public ObjectMapper(JsonFactory jf, - SerializerProvider sp, DeserializerProvider dp) - { - this(jf, sp, dp, null, null); - } - /** + * Constructs instance that uses specified {@link JsonFactory} + * for constructing necessary {@link JsonParser}s and/or + * {@link JsonGenerator}s, and uses given providers for accessing + * serializers and deserializers. * * @param jf JsonFactory to use: if null, a new {@link MappingJsonFactory} will be constructed * @param sp SerializerProvider to use: if null, a {@link StdSerializerProvider} will be constructed * @param dp DeserializerProvider to use: if null, a {@link StdDeserializerProvider} will be constructed - * @param sconfig Serialization configuration to use; if null, basic {@link SerializationConfig} - * will be constructed - * @param dconfig Deserialization configuration to use; if null, basic {@link DeserializationConfig} - * will be constructed */ public ObjectMapper(JsonFactory jf, - SerializerProvider sp, DeserializerProvider dp, - SerializationConfig sconfig, DeserializationConfig dconfig) + SerializerProvider sp, DeserializerProvider dp) { /* 02-Mar-2009, tatu: Important: we MUST default to using * the mapping factory, otherwise tree serialization will @@ -399,11 +390,9 @@ public class ObjectMapper _subtypeResolver = new StdSubtypeResolver(); // and default type factory is shared one _typeFactory = TypeFactory.defaultInstance(); - _serializationConfig = (sconfig != null) ? sconfig : - new SerializationConfig(DEFAULT_BASE, + _serializationConfig = new SerializationConfig(DEFAULT_BASE, _subtypeResolver, _mixInAnnotations); - _deserializationConfig = (dconfig != null) ? dconfig : - new DeserializationConfig(DEFAULT_BASE, + _deserializationConfig = new DeserializationConfig(DEFAULT_BASE, _subtypeResolver, _mixInAnnotations); _serializerProvider = (sp == null) ? new StdSerializerProvider.Impl() : sp; _deserializerProvider = (dp == null) ? new StdDeserializerProvider() : dp; @@ -601,15 +590,6 @@ public class ObjectMapper public SerializationConfig getSerializationConfig() { return _serializationConfig; } - - /** - * Method for replacing the shared default serialization configuration - * object. - */ - public ObjectMapper setSerializationConfig(SerializationConfig cfg) { - _serializationConfig = cfg; - return this; - } /** * Method that returns @@ -622,15 +602,6 @@ public class ObjectMapper public DeserializationConfig getDeserializationConfig() { return _deserializationConfig; } - - /** - * Method for replacing the shared default deserialization configuration - * object. - */ - public ObjectMapper setDeserializationConfig(DeserializationConfig cfg) { - _deserializationConfig = cfg; - return this; - } /* /********************************************************** @@ -2660,8 +2631,8 @@ public class ObjectMapper DeserializationContext ctxt = _createDeserializationContext(jp, cfg); JsonDeserializer<Object> deser = _findRootDeserializer(cfg, valueType); // ok, let's get the value - if (cfg.isEnabled(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE)) { - result = _unwrapAndDeserialize(jp, valueType, ctxt, deser); + if (cfg.useRootWrapping()) { + result = _unwrapAndDeserialize(jp, ctxt, cfg, valueType, deser); } else { result = deser.deserialize(jp, ctxt); } @@ -2686,8 +2657,8 @@ public class ObjectMapper DeserializationConfig cfg = getDeserializationConfig(); DeserializationContext ctxt = _createDeserializationContext(jp, cfg); JsonDeserializer<Object> deser = _findRootDeserializer(cfg, valueType); - if (cfg.isEnabled(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE)) { - result = _unwrapAndDeserialize(jp, valueType, ctxt, deser); + if (cfg.useRootWrapping()) { + result = _unwrapAndDeserialize(jp, ctxt, cfg, valueType, deser); } else { result = deser.deserialize(jp, ctxt); } @@ -2738,32 +2709,36 @@ public class ObjectMapper return t; } - protected Object _unwrapAndDeserialize(JsonParser jp, JavaType rootType, - DeserializationContext ctxt, JsonDeserializer<Object> deser) + protected Object _unwrapAndDeserialize(JsonParser jp, DeserializationContext ctxt, + DeserializationConfig config, + JavaType rootType, JsonDeserializer<Object> deser) throws IOException, JsonParseException, JsonMappingException { - SerializedString rootName = _deserializerProvider.findExpectedRootName(ctxt.getConfig(), rootType); + String expName = config.getRootName(); + if (expName == null) { + SerializedString sstr = _deserializerProvider.findExpectedRootName(config, rootType); + expName = sstr.getValue(); + } if (jp.getCurrentToken() != JsonToken.START_OBJECT) { throw JsonMappingException.from(jp, "Current token not START_OBJECT (needed to unwrap root name '" - +rootName+"'), but "+jp.getCurrentToken()); + +expName+"'), but "+jp.getCurrentToken()); } if (jp.nextToken() != JsonToken.FIELD_NAME) { throw JsonMappingException.from(jp, "Current token not FIELD_NAME (to contain expected root name '" - +rootName+"'), but "+jp.getCurrentToken()); + +expName+"'), but "+jp.getCurrentToken()); } String actualName = jp.getCurrentName(); - if (!rootName.getValue().equals(actualName)) { - throw JsonMappingException.from(jp, "Root name '"+actualName+"' does not match expected ('"+rootName - +"') for type "+rootType); + if (!expName.equals(actualName)) { + throw JsonMappingException.from(jp, "Root name '"+actualName+"' does not match expected ('" + +expName+"') for type "+rootType); } // ok, then move to value itself.... jp.nextToken(); - Object result = deser.deserialize(jp, ctxt); // and last, verify that we now get matching END_OBJECT if (jp.nextToken() != JsonToken.END_OBJECT) { throw JsonMappingException.from(jp, "Current token not END_OBJECT (to match wrapper object with root name '" - +rootName+"'), but "+jp.getCurrentToken()); + +expName+"'), but "+jp.getCurrentToken()); } return result; } diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java index ef511ab88..9b5bdd59c 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectReader.java @@ -126,7 +126,7 @@ public class ObjectReader } _schema = schema; _injectableValues = injectableValues; - _unwrapRoot = config.isEnabled(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE); + _unwrapRoot = config.useRootWrapping(); } /** @@ -149,7 +149,7 @@ public class ObjectReader } _schema = schema; _injectableValues = injectableValues; - _unwrapRoot = config.isEnabled(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE); + _unwrapRoot = config.useRootWrapping(); } /** @@ -167,7 +167,7 @@ public class ObjectReader _valueToUpdate = base._valueToUpdate; _schema = base._schema; _injectableValues = base._injectableValues; - _unwrapRoot = config.isEnabled(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE); + _unwrapRoot = config.useRootWrapping(); } /** @@ -226,6 +226,68 @@ public class ObjectReader DeserializationConfig newConfig = _config.without(first, other); return (newConfig == _config) ? this : new ObjectReader(this, newConfig); } + + /** + * Method for constructing a new instance with configuration that uses + * passed {@link InjectableValues} to provide injectable values. + *<p> + * Note that the method does NOT change state of this reader, but + * rather construct and returns a newly configured instance. + */ + public ObjectReader withInjectableValues(InjectableValues injectableValues) + { + if (_injectableValues == injectableValues) { + return this; + } + return new ObjectReader(this, _config, _valueType, _valueToUpdate, + _schema, injectableValues); + } + + /** + * Method for constructing a new reader instance with configuration that uses + * passed {@link JsonNodeFactory} for constructing {@link JsonNode} + * instances. + *<p> + * Note that the method does NOT change state of this reader, but + * rather construct and returns a newly configured instance. + */ + public ObjectReader withNodeFactory(JsonNodeFactory f) + { + DeserializationConfig newConfig = _config.withNodeFactory(f); + return (newConfig == _config) ? this : new ObjectReader(this, newConfig); + } + + /** + * Method for constructing a new instance with configuration that + * specifies what root name to expect for "root name unwrapping". + * See {@link DeserializationConfig#withRootName(String)} for + * details. + *<p> + * Note that the method does NOT change state of this reader, but + * rather construct and returns a newly configured instance. + */ + public ObjectReader withRootName(String rootName) + { + DeserializationConfig newConfig = _config.withRootName(rootName); + return (newConfig == _config) ? this : new ObjectReader(this, newConfig); + } + + /** + * Method for constructing a new instance with configuration that + * passes specified {@link FormatSchema} to {@link JsonParser} that + * is constructed for parsing content. + *<p> + * Note that the method does NOT change state of this reader, but + * rather construct and returns a newly configured instance. + */ + public ObjectReader withSchema(FormatSchema schema) + { + if (_schema == schema) { + return this; + } + return new ObjectReader(this, _config, _valueType, _valueToUpdate, + schema, _injectableValues); + } /** * Method for constructing a new reader instance that is configured @@ -276,22 +338,6 @@ public class ObjectReader { return withType(_config.getTypeFactory().constructType(valueTypeRef.getType())); } - - /** - * Method for constructing a new reader instance with configuration that uses - * passed {@link JsonNodeFactory} for constructing {@link JsonNode} - * instances. - *<p> - * Note that the method does NOT change state of this reader, but - * rather construct and returns a newly configured instance. - */ - public ObjectReader withNodeFactory(JsonNodeFactory f) - { - // node factory is stored within config, so need to copy that first - if (f == _config.getNodeFactory()) return this; - return new ObjectReader(this, _config.withNodeFactory(f), _valueType, _valueToUpdate, - _schema, _injectableValues); - } /** * Method for constructing a new instance with configuration that @@ -311,39 +357,6 @@ public class ObjectReader return new ObjectReader(this, _config, t, value, _schema, _injectableValues); } - - /** - * Method for constructing a new instance with configuration that - * passes specified {@link FormatSchema} to {@link JsonParser} that - * is constructed for parsing content. - *<p> - * Note that the method does NOT change state of this reader, but - * rather construct and returns a newly configured instance. - */ - public ObjectReader withSchema(FormatSchema schema) - { - if (_schema == schema) { - return this; - } - return new ObjectReader(this, _config, _valueType, _valueToUpdate, - schema, _injectableValues); - } - - /** - * Method for constructing a new instance with configuration that uses - * passed {@link InjectableValues} to provide injectable values. - *<p> - * Note that the method does NOT change state of this reader, but - * rather construct and returns a newly configured instance. - */ - public ObjectReader withInjectableValues(InjectableValues injectableValues) - { - if (_injectableValues == injectableValues) { - return this; - } - return new ObjectReader(this, _config, _valueType, _valueToUpdate, - _schema, injectableValues); - } /* /********************************************************** @@ -916,19 +929,23 @@ public class ObjectReader JavaType rootType, JsonDeserializer<Object> deser) throws IOException, JsonParseException, JsonMappingException { - SerializedString rootName = _provider.findExpectedRootName(ctxt.getConfig(), rootType); + String expName = _config.getRootName(); + if (expName == null) { + SerializedString sstr = _provider.findExpectedRootName(_config, rootType); + expName = sstr.getValue(); + } if (jp.getCurrentToken() != JsonToken.START_OBJECT) { throw JsonMappingException.from(jp, "Current token not START_OBJECT (needed to unwrap root name '" - +rootName+"'), but "+jp.getCurrentToken()); + +expName+"'), but "+jp.getCurrentToken()); } if (jp.nextToken() != JsonToken.FIELD_NAME) { throw JsonMappingException.from(jp, "Current token not FIELD_NAME (to contain expected root name '" - +rootName+"'), but "+jp.getCurrentToken()); + +expName+"'), but "+jp.getCurrentToken()); } String actualName = jp.getCurrentName(); - if (!rootName.getValue().equals(actualName)) { - throw JsonMappingException.from(jp, "Root name '"+actualName+"' does not match expected ('"+rootName - +"') for type "+rootType); + if (!expName.equals(actualName)) { + throw JsonMappingException.from(jp, "Root name '"+actualName+"' does not match expected ('" + +expName+"') for type "+rootType); } // ok, then move to value itself.... jp.nextToken(); @@ -942,7 +959,7 @@ public class ObjectReader // and last, verify that we now get matching END_OBJECT if (jp.nextToken() != JsonToken.END_OBJECT) { throw JsonMappingException.from(jp, "Current token not END_OBJECT (to match wrapper object with root name '" - +rootName+"'), but "+jp.getCurrentToken()); + +expName+"'), but "+jp.getCurrentToken()); } return result; } diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectWriter.java b/src/main/java/com/fasterxml/jackson/databind/ObjectWriter.java index b7d63fc01..dbe1e4f99 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ObjectWriter.java +++ b/src/main/java/com/fasterxml/jackson/databind/ObjectWriter.java @@ -151,8 +151,6 @@ public class ObjectWriter /** * Copy constructor used for building variations. - * - * @since 1.7 */ protected ObjectWriter(ObjectWriter base, SerializationConfig config) { @@ -223,45 +221,42 @@ public class ObjectWriter SerializationConfig newConfig = _config.without(first, other); return (newConfig == _config) ? this : new ObjectWriter(this, newConfig); } - + /** - * Method that will construct a new instance that uses specified - * serialization view for serialization (with null basically disables - * view processing) + * Fluent factory method that will construct a new writer instance that will + * use specified date format for serializing dates; or if null passed, one + * that will serialize dates as numeric timestamps. + *<p> + * Note that the method does NOT change state of this reader, but + * rather construct and returns a newly configured instance. */ - public ObjectWriter withView(Class<?> view) + public ObjectWriter withDateFormat(DateFormat df) { - if (view == _config.getSerializationView()) return this; - return new ObjectWriter(this, _config.withView(view)); - } - + SerializationConfig newConfig = _config.withDateFormat(df); + return (newConfig == _config) ? this : new ObjectWriter(this, newConfig); + } + /** - * Method that will construct a new instance that uses specific type - * as the root type for serialization, instead of runtime dynamic - * type of the root object itself. + * Method that will construct a new instance that will use the default + * pretty printer for serialization. */ - public ObjectWriter withType(JavaType rootType) + public ObjectWriter withDefaultPrettyPrinter() { - if (rootType == _rootType) return this; - // type is stored here, no need to make a copy of config - return new ObjectWriter(this, _config, rootType, _prettyPrinter, _schema); - } + return withPrettyPrinter(new DefaultPrettyPrinter()); + } /** - * Method that will construct a new instance that uses specific type - * as the root type for serialization, instead of runtime dynamic - * type of the root object itself. + * Method that will construct a new instance that uses specified + * provider for resolving filter instances by id. */ - public ObjectWriter withType(Class<?> rootType) + public ObjectWriter withFilters(FilterProvider filterProvider) { - return withType(_config.constructType(rootType)); + if (filterProvider == _config.getFilterProvider()) { // no change? + return this; + } + return new ObjectWriter(this, _config.withFilters(filterProvider)); } - public ObjectWriter withType(TypeReference<?> rootType) - { - return withType(_config.getTypeFactory().constructType(rootType.getType())); - } - /** * Method that will construct a new instance that will use specified pretty * printer (or, if null, will not do any pretty-printing) @@ -279,56 +274,74 @@ public class ObjectWriter } /** - * Method that will construct a new instance that will use the default - * pretty printer for serialization. - * - * @since 1.6 + * Method for constructing a new instance with configuration that + * specifies what root name to use for "root element wrapping". + * See {@link SerializationConfig#withRootName(String)} for details. + *<p> + * Note that method does NOT change state of this reader, but + * rather construct and returns a newly configured instance. */ - public ObjectWriter withDefaultPrettyPrinter() + public ObjectWriter withRootName(String rootName) { - return withPrettyPrinter(new DefaultPrettyPrinter()); + SerializationConfig newConfig = _config.withRootName(rootName); + return (newConfig == _config) ? this : new ObjectWriter(this, newConfig); } /** - * Method that will construct a new instance that uses specified - * provider for resolving filter instances by id. - * - * @since 1.7 + * Method that will construct a new instance that uses specific format schema + * for serialization. + *<p> + * Note that method does NOT change state of this reader, but + * rather construct and returns a newly configured instance. */ - public ObjectWriter withFilters(FilterProvider filterProvider) + + public ObjectWriter withSchema(FormatSchema schema) { - if (filterProvider == _config.getFilterProvider()) { // no change? - return this; - } - return new ObjectWriter(this, _config.withFilters(filterProvider)); + return (_schema == schema) ? this : + new ObjectWriter(this, _config, _rootType, _prettyPrinter, schema); } - + /** - * @since 1.8 + * Method that will construct a new instance that uses specific type + * as the root type for serialization, instead of runtime dynamic + * type of the root object itself. + *<p> + * Note that method does NOT change state of this reader, but + * rather construct and returns a newly configured instance. */ - public ObjectWriter withSchema(FormatSchema schema) + public ObjectWriter withType(JavaType rootType) { - if (_schema == schema) { - return this; - } - return new ObjectWriter(this, _config, _rootType, _prettyPrinter, schema); + return (rootType == _rootType) ? this + // type is stored here, no need to make a copy of config + : new ObjectWriter(this, _config, rootType, _prettyPrinter, _schema); + } + + /** + * Method that will construct a new instance that uses specific type + * as the root type for serialization, instead of runtime dynamic + * type of the root object itself. + */ + public ObjectWriter withType(Class<?> rootType) { + return withType(_config.constructType(rootType)); + } + + public ObjectWriter withType(TypeReference<?> rootType) { + return withType(_config.getTypeFactory().constructType(rootType.getType())); } /** - * Fluent factory method that will construct a new writer instance that will - * use specified date format for serializing dates; or if null passed, one - * that will serialize dates as numeric timestamps. - * - * @since 1.9 + * Method that will construct a new instance that uses specified + * serialization view for serialization (with null basically disables + * view processing) + *<p> + * Note that the method does NOT change state of this reader, but + * rather construct and returns a newly configured instance. */ - public ObjectWriter withDateFormat(DateFormat df) + public ObjectWriter withView(Class<?> view) { - SerializationConfig newConfig = _config.withDateFormat(df); - if (newConfig == _config) { - return this; - } - return new ObjectWriter(this, newConfig); - } + return (view == _config.getSerializationView()) ? this + : new ObjectWriter(this, _config.withView(view)); + } /* /********************************************************** diff --git a/src/main/java/com/fasterxml/jackson/databind/SerializationConfig.java b/src/main/java/com/fasterxml/jackson/databind/SerializationConfig.java index e3cc63244..3c7c6b0a5 100644 --- a/src/main/java/com/fasterxml/jackson/databind/SerializationConfig.java +++ b/src/main/java/com/fasterxml/jackson/databind/SerializationConfig.java @@ -5,10 +5,7 @@ import java.util.*; import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; -import com.fasterxml.jackson.databind.cfg.BaseSettings; -import com.fasterxml.jackson.databind.cfg.ConfigFeature; -import com.fasterxml.jackson.databind.cfg.MapperConfig; -import com.fasterxml.jackson.databind.cfg.MapperConfigBase; +import com.fasterxml.jackson.databind.cfg.*; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.introspect.ClassIntrospector; import com.fasterxml.jackson.databind.introspect.VisibilityChecker; @@ -32,7 +29,7 @@ import com.fasterxml.jackson.databind.util.ClassUtil; * with respect to mix-in annotations; where this is guaranteed as * long as caller follow "copy-then-use" pattern) */ -public class SerializationConfig +public final class SerializationConfig extends MapperConfigBase<SerializationConfig.Feature, SerializationConfig> { /** @@ -384,6 +381,15 @@ public class SerializationConfig _serializationView = src._serializationView; _filterProvider = src._filterProvider; } + + private SerializationConfig(SerializationConfig src, String rootName) + { + super(src, rootName); + _serFeatures = src._serFeatures; + _serializationInclusion = src._serializationInclusion; + _serializationView = src._serializationView; + _filterProvider = src._filterProvider; + } /* /********************************************************** @@ -465,6 +471,18 @@ public class SerializationConfig public SerializationConfig withPropertyNamingStrategy(PropertyNamingStrategy pns) { return _withBase(_base.withPropertyNamingStrategy(pns)); } + + @Override + public SerializationConfig withRootName(String rootName) { + if (rootName == null) { + if (_rootName == null) { + return this; + } + } else if (rootName.equals(_rootName)) { + return this; + } + return new SerializationConfig(this, rootName); + } @Override public SerializationConfig withTypeFactory(TypeFactory tf) { @@ -577,6 +595,15 @@ public class SerializationConfig } @Override + public boolean useRootWrapping() + { + if (_rootName != null) { // empty String disables wrapping; non-empty enables + return (_rootName.length() > 0); + } + return isEnabled(SerializationConfig.Feature.WRAP_ROOT_VALUE); + } + + @Override public AnnotationIntrospector getAnnotationIntrospector() { /* 29-Jul-2009, tatu: it's now possible to disable use of diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java index 5a34dc037..b5fb47c53 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java @@ -313,7 +313,6 @@ public abstract class MapperConfig<T extends MapperConfig<T>> } return flags; } - /* /********************************************************** /* Life-cycle: factory methods @@ -332,116 +331,6 @@ public abstract class MapperConfig<T extends MapperConfig<T>> */ public abstract T without(MapperConfig.Feature... features); - /** - * Method for constructing and returning a new instance with different - * {@link ClassIntrospector} - * to use. - *<p> - * NOTE: make sure to register new instance with <code>ObjectMapper</code> - * if directly calling this method. - */ - public abstract T withClassIntrospector(ClassIntrospector<? extends BeanDescription> ci); - - /** - * Method for constructing and returning a new instance with different - * {@link AnnotationIntrospector} to use (replacing old one). - *<p> - * NOTE: make sure to register new instance with <code>ObjectMapper</code> - * if directly calling this method. - */ - public abstract T withAnnotationIntrospector(AnnotationIntrospector ai); - - /** - * Method for constructing and returning a new instance with different - * {@link VisibilityChecker} - * to use. - *<p> - * NOTE: make sure to register new instance with <code>ObjectMapper</code> - * if directly calling this method. - */ - public abstract T withVisibilityChecker(VisibilityChecker<?> vc); - - /** - * Method for constructing and returning a new instance with different - * minimal visibility level for specified property type - *<p> - * NOTE: make sure to register new instance with <code>ObjectMapper</code> - * if directly calling this method. - */ - public abstract T withVisibility(PropertyAccessor forMethod, JsonAutoDetect.Visibility visibility); - - /** - * Method for constructing and returning a new instance with different - * {@link TypeResolverBuilder} - * to use. - *<p> - * NOTE: make sure to register new instance with <code>ObjectMapper</code> - * if directly calling this method. - */ - public abstract T withTypeResolverBuilder(TypeResolverBuilder<?> trb); - - /** - * Method for constructing and returning a new instance with different - * {@link SubtypeResolver} - * to use. - *<p> - * NOTE: make sure to register new instance with <code>ObjectMapper</code> - * if directly calling this method. - */ - public abstract T withSubtypeResolver(SubtypeResolver str); - - /** - * Method for constructing and returning a new instance with different - * {@link PropertyNamingStrategy} - * to use. - *<p> - * NOTE: make sure to register new instance with <code>ObjectMapper</code> - * if directly calling this method. - */ - public abstract T withPropertyNamingStrategy(PropertyNamingStrategy strategy); - - /** - * Method for constructing and returning a new instance with different - * {@link TypeFactory} - * to use. - *<p> - * NOTE: make sure to register new instance with <code>ObjectMapper</code> - * if directly calling this method. - */ - public abstract T withTypeFactory(TypeFactory typeFactory); - - /** - * Method for constructing and returning a new instance with different - * {@link DateFormat} - * to use. - *<p> - * NOTE: make sure to register new instance with <code>ObjectMapper</code> - * if directly calling this method. - */ - public abstract T withDateFormat(DateFormat df); - - /** - * Method for constructing and returning a new instance with different - * {@link HandlerInstantiator} - * to use. - *<p> - * NOTE: make sure to register new instance with <code>ObjectMapper</code> - * if directly calling this method. - */ - public abstract T withHandlerInstantiator(HandlerInstantiator hi); - - /** - * Method for constructing and returning a new instance with additional - * {@link AnnotationIntrospector} inserted (as the highest priority one) - */ - public abstract T withInsertedAnnotationIntrospector(AnnotationIntrospector introspector); - - /** - * Method for constructing and returning a new instance with additional - * {@link AnnotationIntrospector} appended (as the lowest priority one) - */ - public abstract T withAppendedAnnotationIntrospector(AnnotationIntrospector introspector); - /* /********************************************************** /* Configuration: simple features @@ -488,6 +377,13 @@ public abstract class MapperConfig<T extends MapperConfig<T>> public final boolean shouldSortPropertiesAlphabetically() { return isEnabled(MapperConfig.Feature.SORT_PROPERTIES_ALPHABETICALLY); } + + /** + * Accessor for checking whether configuration indicates that + * "root wrapping" (use of an extra property/name pair at root level) + * is expected or not. + */ + public abstract boolean useRootWrapping(); /* /********************************************************** 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 2dfafd0d6..79add3375 100644 --- a/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java +++ b/src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfigBase.java @@ -1,9 +1,20 @@ package com.fasterxml.jackson.databind.cfg; +import java.text.DateFormat; import java.util.Map; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.AnnotationIntrospector; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.HandlerInstantiator; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.introspect.ClassIntrospector; +import com.fasterxml.jackson.databind.introspect.VisibilityChecker; import com.fasterxml.jackson.databind.jsontype.SubtypeResolver; +import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import com.fasterxml.jackson.databind.type.ClassKey; +import com.fasterxml.jackson.databind.type.TypeFactory; public abstract class MapperConfigBase<CFG extends ConfigFeature, T extends MapperConfigBase<CFG,T>> @@ -28,6 +39,13 @@ public abstract class MapperConfigBase<CFG extends ConfigFeature, * in addition to) ones declared using annotations. */ protected final SubtypeResolver _subtypeResolver; + + /** + * Explicitly definite root name to use, if any; if empty + * String, will disable root-name wrapping; if null, will + * use defaults + */ + protected final String _rootName; /* /********************************************************** @@ -45,6 +63,7 @@ public abstract class MapperConfigBase<CFG extends ConfigFeature, super(base, DEFAULT_MAPPER_FEATURES); _mixInAnnotations = mixins; _subtypeResolver = str; + _rootName = null; } /** @@ -55,6 +74,15 @@ public abstract class MapperConfigBase<CFG extends ConfigFeature, super(src); _mixInAnnotations = src._mixInAnnotations; _subtypeResolver = src._subtypeResolver; + _rootName = src._rootName; + } + + protected MapperConfigBase(MapperConfigBase<CFG,T> src, BaseSettings base) + { + super(base, src._mapperFeatures); + _mixInAnnotations = src._mixInAnnotations; + _subtypeResolver = src._subtypeResolver; + _rootName = src._rootName; } protected MapperConfigBase(MapperConfigBase<CFG,T> src, int mapperFeatures) @@ -62,21 +90,142 @@ public abstract class MapperConfigBase<CFG extends ConfigFeature, super(src._base, mapperFeatures); _mixInAnnotations = src._mixInAnnotations; _subtypeResolver = src._subtypeResolver; + _rootName = src._rootName; } protected MapperConfigBase(MapperConfigBase<CFG,T> src, SubtypeResolver str) { super(src); _mixInAnnotations = src._mixInAnnotations; _subtypeResolver = str; + _rootName = src._rootName; } - protected MapperConfigBase(MapperConfigBase<CFG,T> src, BaseSettings base) - { - super(base, src._mapperFeatures); + protected MapperConfigBase(MapperConfigBase<CFG,T> src, String rootName) { + super(src); _mixInAnnotations = src._mixInAnnotations; _subtypeResolver = src._subtypeResolver; + _rootName = rootName; } + + /* + /********************************************************** + /* Addition fluent factory methods, common to all sub-types + /********************************************************** + */ + + /** + * Method for constructing and returning a new instance with different + * {@link AnnotationIntrospector} to use (replacing old one). + *<p> + * NOTE: make sure to register new instance with <code>ObjectMapper</code> + * if directly calling this method. + */ + public abstract T withAnnotationIntrospector(AnnotationIntrospector ai); + + /** + * Method for constructing and returning a new instance with additional + * {@link AnnotationIntrospector} appended (as the lowest priority one) + */ + public abstract T withAppendedAnnotationIntrospector(AnnotationIntrospector introspector); + + /** + * Method for constructing and returning a new instance with additional + * {@link AnnotationIntrospector} inserted (as the highest priority one) + */ + public abstract T withInsertedAnnotationIntrospector(AnnotationIntrospector introspector); + + /** + * Method for constructing and returning a new instance with different + * {@link ClassIntrospector} + * to use. + *<p> + * NOTE: make sure to register new instance with <code>ObjectMapper</code> + * if directly calling this method. + */ + public abstract T withClassIntrospector(ClassIntrospector<? extends BeanDescription> ci); + + /** + * Method for constructing and returning a new instance with different + * {@link DateFormat} + * to use. + *<p> + * NOTE: make sure to register new instance with <code>ObjectMapper</code> + * if directly calling this method. + */ + public abstract T withDateFormat(DateFormat df); + + /** + * Method for constructing and returning a new instance with different + * {@link HandlerInstantiator} + * to use. + *<p> + * NOTE: make sure to register new instance with <code>ObjectMapper</code> + * if directly calling this method. + */ + public abstract T withHandlerInstantiator(HandlerInstantiator hi); + + /** + * Method for constructing and returning a new instance with different + * {@link PropertyNamingStrategy} + * to use. + *<p> + * NOTE: make sure to register new instance with <code>ObjectMapper</code> + * if directly calling this method. + */ + public abstract T withPropertyNamingStrategy(PropertyNamingStrategy strategy); + + /** + * Method for constructing and returning a new instance with different + * root name to use (none, if null). + *<p> + * Note that when a root name is set to a non-Empty String, this will automatically force use + * of root element wrapping with given name. If empty String passed, will + * disable root name wrapping; and if null used, will instead use + * <code>Feature</code> to determine if to use wrapping, and annotation + * (or default name) for actual root name to use. + * + * @param rootName to use: if null, means "use default" (clear setting); + * if empty String ("") means that no root name wrapping is used; + * otherwise defines root name to use. + */ + public abstract T withRootName(String rootName); + + /** + * Method for constructing and returning a new instance with different + * {@link SubtypeResolver} + * to use. + *<p> + * NOTE: make sure to register new instance with <code>ObjectMapper</code> + * if directly calling this method. + */ + public abstract T withSubtypeResolver(SubtypeResolver str); + + /** + * Method for constructing and returning a new instance with different + * {@link TypeFactory} + * to use. + */ + public abstract T withTypeFactory(TypeFactory typeFactory); + /** + * Method for constructing and returning a new instance with different + * {@link TypeResolverBuilder} to use. + */ + public abstract T withTypeResolverBuilder(TypeResolverBuilder<?> trb); + + /** + * Method for constructing and returning a new instance with different + * {@link VisibilityChecker} + * to use. + */ + public abstract T withVisibilityChecker(VisibilityChecker<?> vc); + + /** + * Method for constructing and returning a new instance with different + * minimal visibility level for specified property type + */ + public abstract T withVisibility(PropertyAccessor forMethod, JsonAutoDetect.Visibility visibility); + /* /********************************************************** /* Simple accessors @@ -93,6 +242,10 @@ public abstract class MapperConfigBase<CFG extends ConfigFeature, public final SubtypeResolver getSubtypeResolver() { return _subtypeResolver; } + + public final String getRootName() { + return _rootName; + } /* /********************************************************** diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/StdSerializerProvider.java b/src/main/java/com/fasterxml/jackson/databind/ser/StdSerializerProvider.java index 4dc6f86d7..4eeb46b55 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/StdSerializerProvider.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/StdSerializerProvider.java @@ -570,7 +570,7 @@ public abstract class StdSerializerProvider * Method called on the actual non-blueprint provider instance object, * to kick off the serialization. */ - protected void _serializeValue(JsonGenerator jgen, Object value) + protected final void _serializeValue(JsonGenerator jgen, Object value) throws IOException, JsonProcessingException { JsonSerializer<Object> ser; @@ -584,11 +584,23 @@ public abstract class StdSerializerProvider Class<?> cls = value.getClass(); // true, since we do want to cache root-level typed serializers (ditto for null property) ser = findTypedValueSerializer(cls, true, null); - // [JACKSON-163] - wrap = _config.isEnabled(SerializationConfig.Feature.WRAP_ROOT_VALUE); - if (wrap) { + + // Ok: should we wrap result in an additional property ("root name")? + String rootName = _config.getRootName(); + if (rootName == null) { // not explicitly specified + // [JACKSON-163] + wrap = _config.isEnabled(SerializationConfig.Feature.WRAP_ROOT_VALUE); + if (wrap) { + jgen.writeStartObject(); + jgen.writeFieldName(_rootNames.findRootName(value.getClass(), _config)); + } + } else if (rootName.length() == 0) { + wrap = false; + } else { // [JACKSON-764] + // empty String means explicitly disabled; non-empty that it is enabled + wrap = true; jgen.writeStartObject(); - jgen.writeFieldName(_rootNames.findRootName(value.getClass(), _config)); + jgen.writeFieldName(rootName); } } try { |