package com.fasterxml.jackson.databind;
import java.io.*;
import java.net.URL;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.ResolvedType;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.cfg.ContextAttributes;
import com.fasterxml.jackson.databind.deser.DataFormatReaders;
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.TreeTraversingParser;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.RootNameLookup;
/**
* Builder object that can be used for per-serialization configuration of
* deserialization parameters, such as root type to use or object
* to update (instead of constructing new instance).
*
* Uses "fluent" (or, kind of, builder) pattern so that instances are immutable
* (and thus fully thread-safe with no external synchronization);
* new instances are constructed for different configurations.
* Instances are initially constructed by {@link ObjectMapper} and can be
* reused, shared, cached; both because of thread-safety and because
* instances are relatively light-weight.
*/
public class ObjectReader
extends ObjectCodec
implements Versioned, java.io.Serializable // since 2.1
{
private static final long serialVersionUID = -4251443320039569153L;
private final static JavaType JSON_NODE_TYPE = SimpleType.constructUnsafe(JsonNode.class);
/*
/**********************************************************
/* Immutable configuration from ObjectMapper
/**********************************************************
*/
/**
* General serialization configuration settings; while immutable,
* can use copy-constructor to create modified instances as necessary.
*/
protected final DeserializationConfig _config;
/**
* Blueprint instance of deserialization context; used for creating
* actual instance when needed.
*/
protected final DefaultDeserializationContext _context;
/**
* Factory used for constructing {@link JsonGenerator}s
*/
protected final JsonFactory _parserFactory;
/**
* Flag that indicates whether root values are expected to be unwrapped or not
*/
protected final boolean _unwrapRoot;
/*
/**********************************************************
/* Configuration that can be changed during building
/**********************************************************
*/
/**
* Declared type of value to instantiate during deserialization.
* Defines which deserializer to use; as well as base type of instance
* to construct if an updatable value is not configured to be used
* (subject to changes by embedded type information, for polymorphic
* types). If {@link #_valueToUpdate} is non-null, only used for
* locating deserializer.
*/
protected final JavaType _valueType;
/**
* We may pre-fetch deserializer as soon as {@link #_valueType}
* is known, and if so, reuse it afterwards.
* This allows avoiding further deserializer lookups and increases
* performance a bit on cases where readers are reused.
*
* @since 2.1
*/
protected final JsonDeserializer _rootDeserializer;
/**
* Instance to update with data binding; if any. If null,
* a new instance is created, if non-null, properties of
* this value object will be updated instead.
* Note that value can be of almost any type, except not
* {@link com.fasterxml.jackson.databind.type.ArrayType}; array
* types can not be modified because array size is immutable.
*/
protected final Object _valueToUpdate;
/**
* When using data format that uses a schema, schema is passed
* to parser.
*/
protected final FormatSchema _schema;
/**
* Values that can be injected during deserialization, if any.
*/
protected final InjectableValues _injectableValues;
/**
* Optional detector used for auto-detecting data format that byte-based
* input uses.
*
* NOTE: If defined non-null, readValue()
methods that take
* {@link Reader} or {@link String} input will fail with exception ,
* because format-detection only works on byte-sources. Also, if format
* can not be detect reliably (as per detector settings),
* a {@link JsonParseException} will be thrown).
*
* @since 2.1
*/
protected final DataFormatReaders _dataFormatReaders;
/*
/**********************************************************
/* Caching
/**********************************************************
*/
/**
* Root-level cached deserializers
*/
final protected ConcurrentHashMap> _rootDeserializers;
/**
* Cache for root names used when root-wrapping is enabled.
*/
protected final RootNameLookup _rootNames;
/*
/**********************************************************
/* Life-cycle, construction
/**********************************************************
*/
/**
* Constructor used by {@link ObjectMapper} for initial instantiation
*/
protected ObjectReader(ObjectMapper mapper, DeserializationConfig config)
{
this(mapper, config, null, null, null, null);
}
/**
* Constructor called when a root deserializer should be fetched based
* on other configuration.
*/
protected ObjectReader(ObjectMapper mapper, DeserializationConfig config,
JavaType valueType, Object valueToUpdate,
FormatSchema schema, InjectableValues injectableValues)
{
_config = config;
_context = mapper._deserializationContext;
_rootDeserializers = mapper._rootDeserializers;
_parserFactory = mapper._jsonFactory;
_rootNames = mapper._rootNames;
_valueType = valueType;
_valueToUpdate = valueToUpdate;
if (valueToUpdate != null && valueType.isArrayType()) {
throw new IllegalArgumentException("Can not update an array value");
}
_schema = schema;
_injectableValues = injectableValues;
_unwrapRoot = config.useRootWrapping();
_rootDeserializer = _prefetchRootDeserializer(config, valueType);
_dataFormatReaders = null;
}
/**
* Copy constructor used for building variations.
*/
protected ObjectReader(ObjectReader base, DeserializationConfig config,
JavaType valueType, JsonDeserializer rootDeser, Object valueToUpdate,
FormatSchema schema, InjectableValues injectableValues,
DataFormatReaders dataFormatReaders)
{
_config = config;
_context = base._context;
_rootDeserializers = base._rootDeserializers;
_parserFactory = base._parserFactory;
_rootNames = base._rootNames;
_valueType = valueType;
_rootDeserializer = rootDeser;
_valueToUpdate = valueToUpdate;
if (valueToUpdate != null && valueType.isArrayType()) {
throw new IllegalArgumentException("Can not update an array value");
}
_schema = schema;
_injectableValues = injectableValues;
_unwrapRoot = config.useRootWrapping();
_dataFormatReaders = dataFormatReaders;
}
/**
* Copy constructor used when modifying simple feature flags
*/
protected ObjectReader(ObjectReader base, DeserializationConfig config)
{
_config = config;
_context = base._context;
_rootDeserializers = base._rootDeserializers;
_parserFactory = base._parserFactory;
_rootNames = base._rootNames;
_valueType = base._valueType;
_rootDeserializer = base._rootDeserializer;
_valueToUpdate = base._valueToUpdate;
_schema = base._schema;
_injectableValues = base._injectableValues;
_unwrapRoot = config.useRootWrapping();
_dataFormatReaders = base._dataFormatReaders;
}
protected ObjectReader(ObjectReader base, JsonFactory f)
{
// may need to override ordering, based on data format capabilities
_config = base._config
.with(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, f.requiresPropertyOrdering());
_context = base._context;
_rootDeserializers = base._rootDeserializers;
_parserFactory = f;
_rootNames = base._rootNames;
_valueType = base._valueType;
_rootDeserializer = base._rootDeserializer;
_valueToUpdate = base._valueToUpdate;
_schema = base._schema;
_injectableValues = base._injectableValues;
_unwrapRoot = base._unwrapRoot;
_dataFormatReaders = base._dataFormatReaders;
}
/**
* Method that will return version information stored in and read from jar
* that contains this class.
*/
@Override
public Version version() {
return com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION;
}
/*
/**********************************************************
/* Life-cycle, fluent factory methods
/**********************************************************
*/
public ObjectReader with(DeserializationConfig config) {
return _with(config);
}
/**
* Method for constructing a new reader instance that is configured
* with specified feature enabled.
*/
public ObjectReader with(DeserializationFeature feature) {
return _with(_config.with(feature));
}
/**
* Method for constructing a new reader instance that is configured
* with specified features enabled.
*/
public ObjectReader with(DeserializationFeature first,
DeserializationFeature... other)
{
return _with(_config.with(first, other));
}
/**
* Method for constructing a new reader instance that is configured
* with specified features enabled.
*/
public ObjectReader withFeatures(DeserializationFeature... features) {
return _with(_config.withFeatures(features));
}
/**
* Method for constructing a new reader instance that is configured
* with specified feature disabled.
*/
public ObjectReader without(DeserializationFeature feature) {
return _with(_config.without(feature));
}
/**
* Method for constructing a new reader instance that is configured
* with specified features disabled.
*/
public ObjectReader without(DeserializationFeature first,
DeserializationFeature... other)
{
return _with(_config.without(first, other));
}
/**
* Method for constructing a new reader instance that is configured
* with specified features disabled.
*/
public ObjectReader withoutFeatures(DeserializationFeature... features) {
return _with(_config.withoutFeatures(features));
}
/**
* Method for constructing a new instance with configuration that uses
* passed {@link InjectableValues} to provide injectable values.
*
* Note that the method does NOT change state of this reader, but
* rather construct and returns a newly configured instance.
*/
public ObjectReader with(InjectableValues injectableValues)
{
if (_injectableValues == injectableValues) {
return this;
}
return new ObjectReader(this, _config,
_valueType, _rootDeserializer, _valueToUpdate,
_schema, injectableValues, _dataFormatReaders);
}
/**
* Method for constructing a new reader instance with configuration that uses
* passed {@link JsonNodeFactory} for constructing {@link JsonNode}
* instances.
*
* Note that the method does NOT change state of this reader, but
* rather construct and returns a newly configured instance.
*/
public ObjectReader with(JsonNodeFactory f) {
return _with(_config.with(f));
}
/**
* Method for constructing a new reader instance with configuration that uses
* passed {@link JsonFactory} for constructing underlying Readers.
*
* NOTE: only factories that DO NOT REQUIRE SPECIAL MAPPERS
* (that is, ones that return false
for
* {@link JsonFactory#requiresCustomCodec()}) can be used: trying
* to use one that requires custom codec will throw exception
*
* @since 2.1
*/
public ObjectReader with(JsonFactory f) {
if (f == _parserFactory) {
return this;
}
ObjectReader r = new ObjectReader(this, f);
// Also, try re-linking, if possible...
if (f.getCodec() == null) {
f.setCodec(r);
}
return r;
}
/**
* 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.
*
* 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) {
return _with(_config.withRootName(rootName));
}
/**
* Method for constructing a new instance with configuration that
* passes specified {@link FormatSchema} to {@link JsonParser} that
* is constructed for parsing content.
*
* Note that the method does NOT change state of this reader, but
* rather construct and returns a newly configured instance.
*/
public ObjectReader with(FormatSchema schema)
{
if (_schema == schema) {
return this;
}
_verifySchemaType(schema);
return new ObjectReader(this, _config, _valueType, _rootDeserializer, _valueToUpdate,
schema, _injectableValues, _dataFormatReaders);
}
/**
* Method for constructing a new reader instance that is configured
* to data bind into specified type.
*
* Note that the method does NOT change state of this reader, but
* rather construct and returns a newly configured instance.
*/
public ObjectReader withType(JavaType valueType)
{
if (valueType != null && valueType.equals(_valueType)) {
return this;
}
JsonDeserializer rootDeser = _prefetchRootDeserializer(_config, valueType);
// type is stored here, no need to make a copy of config
DataFormatReaders det = _dataFormatReaders;
if (det != null) {
det = det.withType(valueType);
}
return new ObjectReader(this, _config, valueType, rootDeser,
_valueToUpdate, _schema, _injectableValues, det);
}
/**
* Method for constructing a new reader instance that is configured
* to data bind into specified type.
*
* Note that the method does NOT change state of this reader, but
* rather construct and returns a newly configured instance.
*/
public ObjectReader withType(Class> valueType) {
return withType(_config.constructType(valueType));
}
/**
* Method for constructing a new reader instance that is configured
* to data bind into specified type.
*
* Note that the method does NOT change state of this reader, but
* rather construct and returns a newly configured instance.
*/
public ObjectReader withType(java.lang.reflect.Type valueType) {
return withType(_config.getTypeFactory().constructType(valueType));
}
/**
* Method for constructing a new reader instance that is configured
* to data bind into specified type.
*
* Note that the method does NOT change state of this reader, but
* rather construct and returns a newly configured instance.
*/
public ObjectReader withType(TypeReference> valueTypeRef) {
return withType(_config.getTypeFactory().constructType(valueTypeRef.getType()));
}
/**
* Method for constructing a new instance with configuration that
* updates passed Object (as root value), instead of constructing
* a new value.
*
* Note that the method does NOT change state of this reader, but
* rather construct and returns a newly configured instance.
*/
public ObjectReader withValueToUpdate(Object value)
{
if (value == _valueToUpdate) return this;
if (value == null) {
throw new IllegalArgumentException("cat not update null value");
}
JavaType t;
/* no real benefit from pre-fetching, as updating readers are much
* less likely to be reused, and value type may also be forced
* with a later chained call...
*/
if (_valueType == null) {
t = _config.constructType(value.getClass());
} else {
t = _valueType;
}
return new ObjectReader(this, _config, t, _rootDeserializer, value,
_schema, _injectableValues, _dataFormatReaders);
}
/**
* Method for constructing a new instance with configuration that
* uses specified View for filtering.
*
* Note that the method does NOT change state of this reader, but
* rather construct and returns a newly configured instance.
*/
public ObjectReader withView(Class> activeView) {
return _with(_config.withView(activeView));
}
public ObjectReader with(Locale l) {
return _with(_config.with(l));
}
public ObjectReader with(TimeZone tz) {
return _with(_config.with(tz));
}
public ObjectReader withHandler(DeserializationProblemHandler h) {
return _with(_config.withHandler(h));
}
public ObjectReader with(Base64Variant defaultBase64) {
return _with(_config.with(defaultBase64));
}
/**
* Fluent factory method for constructing a reader that will try to
* auto-detect underlying data format, using specified list of
* {@link JsonFactory} instances, and default {@link DataFormatReaders} settings
* (for customized {@link DataFormatReaders}, you can construct instance yourself).
* to construct appropriate {@link JsonParser} for actual parsing.
*
* Note: since format detection only works with byte sources, it is possible to
* get a failure from some 'readValue()' methods. Also, if input can not be reliably
* (enough) detected as one of specified types, an exception will be thrown.
*
* Note: not all {@link JsonFactory} types can be passed: specifically, ones that
* require "custom codec" (like XML factory) will not work. Instead, use
* method that takes {@link ObjectReader} instances instead of factories.
*
* @param readers Data formats accepted, in decreasing order of priority (that is,
* matches checked in listed order, first match wins)
*
* @return Newly configured writer instance
*
* @since 2.1
*/
public ObjectReader withFormatDetection(ObjectReader... readers)
{
return withFormatDetection(new DataFormatReaders(readers));
}
/**
* Fluent factory method for constructing a reader that will try to
* auto-detect underlying data format, using specified
* {@link DataFormatReaders}.
*
* NOTE: since format detection only works with byte sources, it is possible to
* get a failure from some 'readValue()' methods. Also, if input can not be reliably
* (enough) detected as one of specified types, an exception will be thrown.
*
* @param readers DataFormatReaders to use for detecting underlying format.
*
* @return Newly configured writer instance
*
* @since 2.1
*/
public ObjectReader withFormatDetection(DataFormatReaders readers)
{
return new ObjectReader(this, _config, _valueType, _rootDeserializer, _valueToUpdate,
_schema, _injectableValues, readers);
}
/**
* @since 2.3
*/
public ObjectReader with(ContextAttributes attrs) {
DeserializationConfig newConfig = _config.with(attrs);
return (newConfig == _config) ? this : new ObjectReader(this, newConfig);
}
/**
* @since 2.3
*/
public ObjectReader withAttributes(Map attrs) {
DeserializationConfig newConfig = _config.withAttributes(attrs);
return (newConfig == _config) ? this : new ObjectReader(this, newConfig);
}
/**
* @since 2.3
*/
public ObjectReader withAttribute(Object key, Object value) {
DeserializationConfig newConfig = _config.withAttribute(key, value);
return (newConfig == _config) ? this : new ObjectReader(this, newConfig);
}
/**
* @since 2.3
*/
public ObjectReader withoutAttribute(Object key) {
DeserializationConfig newConfig = _config.withoutAttribute(key);
return (newConfig == _config) ? this : new ObjectReader(this, newConfig);
}
/*
/**********************************************************
/* Simple accessors
/**********************************************************
*/
public boolean isEnabled(DeserializationFeature f) {
return _config.isEnabled(f);
}
public boolean isEnabled(MapperFeature f) {
return _config.isEnabled(f);
}
public boolean isEnabled(JsonParser.Feature f) {
return _parserFactory.isEnabled(f);
}
/**
* @since 2.2
*/
public DeserializationConfig getConfig() {
return _config;
}
/**
* @since 2.1
*/
@Override
public JsonFactory getFactory() {
return _parserFactory;
}
/**
* @deprecated Since 2.1: Use {@link #getFactory} instead
*/
@Deprecated
@Override
public JsonFactory getJsonFactory() {
return _parserFactory;
}
public TypeFactory getTypeFactory() {
return _config.getTypeFactory();
}
/**
* @since 2.3
*/
public ContextAttributes getAttributes() {
return _config.getAttributes();
}
/*
/**********************************************************
/* Deserialization methods; basic ones to support ObjectCodec first
/* (ones that take JsonParser)
/**********************************************************
*/
/**
* Method that binds content read using given parser, using
* configuration of this reader, including expected result type.
* Value return is either newly constructed, or root value that
* was specified with {@link #withValueToUpdate(Object)}.
*
* NOTE: this method never tries to auto-detect format, since actual
* (data-format specific) parser is given.
*/
@SuppressWarnings("unchecked")
public T readValue(JsonParser jp)
throws IOException, JsonProcessingException
{
return (T) _bind(jp, _valueToUpdate);
}
/**
* Convenience method that binds content read using given parser, using
* configuration of this reader, except that expected value type
* is specified with the call (instead of currently configured root type).
* Value return is either newly constructed, or root value that
* was specified with {@link #withValueToUpdate(Object)}.
*
* NOTE: this method never tries to auto-detect format, since actual
* (data-format specific) parser is given.
*/
@SuppressWarnings("unchecked")
@Override
public T readValue(JsonParser jp, Class valueType)
throws IOException, JsonProcessingException
{
return (T) withType(valueType).readValue(jp);
}
/**
* Convenience method that binds content read using given parser, using
* configuration of this reader, except that expected value type
* is specified with the call (instead of currently configured root type).
* Value return is either newly constructed, or root value that
* was specified with {@link #withValueToUpdate(Object)}.
*
* NOTE: this method never tries to auto-detect format, since actual
* (data-format specific) parser is given.
*/
@SuppressWarnings("unchecked")
@Override
public T readValue(JsonParser jp, TypeReference> valueTypeRef)
throws IOException, JsonProcessingException
{
return (T) withType(valueTypeRef).readValue(jp);
}
/**
* Convenience method that binds content read using given parser, using
* configuration of this reader, except that expected value type
* is specified with the call (instead of currently configured root type).
* Value return is either newly constructed, or root value that
* was specified with {@link #withValueToUpdate(Object)}.
*
* NOTE: this method never tries to auto-detect format, since actual
* (data-format specific) parser is given.
*/
@Override
@SuppressWarnings("unchecked")
public T readValue(JsonParser jp, ResolvedType valueType) throws IOException, JsonProcessingException {
return (T) withType((JavaType)valueType).readValue(jp);
}
/**
* Type-safe overloaded method, basically alias for {@link #readValue(JsonParser, ResolvedType)}.
*
* NOTE: this method never tries to auto-detect format, since actual
* (data-format specific) parser is given.
*/
@SuppressWarnings("unchecked")
public T readValue(JsonParser jp, JavaType valueType) throws IOException, JsonProcessingException {
return (T) withType(valueType).readValue(jp);
}
/**
* Convenience method that is equivalent to:
*
* withType(valueType).readValues(jp);
*
*
* NOTE: this method never tries to auto-detect format, since actual
* (data-format specific) parser is given.
*/
@Override
public Iterator readValues(JsonParser jp, Class valueType)
throws IOException, JsonProcessingException {
return withType(valueType).readValues(jp);
}
/**
* Convenience method that is equivalent to:
*
* withType(valueTypeRef).readValues(jp);
*
*
* NOTE: this method never tries to auto-detect format, since actual
* (data-format specific) parser is given.
*/
@Override
public Iterator readValues(JsonParser jp, TypeReference> valueTypeRef)
throws IOException, JsonProcessingException {
return withType(valueTypeRef).readValues(jp);
}
/**
* Convenience method that is equivalent to:
*
* withType(valueType).readValues(jp);
*
*
* NOTE: this method never tries to auto-detect format, since actual
* (data-format specific) parser is given.
*/
@Override
public Iterator readValues(JsonParser jp, ResolvedType valueType)
throws IOException, JsonProcessingException {
return readValues(jp, (JavaType) valueType);
}
/**
* Convenience method that is equivalent to:
*
* withType(valueType).readValues(jp);
*
*
* NOTE: this method never tries to auto-detect format, since actual
* (data-format specific) parser is given.
*/
public Iterator readValues(JsonParser jp, JavaType valueType)
throws IOException, JsonProcessingException {
return withType(valueType).readValues(jp);
}
/*
/**********************************************************
/* TreeCodec impl
/**********************************************************
*/
@Override
public JsonNode createArrayNode() {
return _config.getNodeFactory().arrayNode();
}
@Override
public JsonNode createObjectNode() {
return _config.getNodeFactory().objectNode();
}
@Override
public JsonParser treeAsTokens(TreeNode n) {
return new TreeTraversingParser((JsonNode) n, this);
}
/**
* Convenience method that binds content read using given parser, using
* configuration of this reader, except that content is bound as
* JSON tree instead of configured root value type.
*
* Note: if an object was specified with {@link #withValueToUpdate}, it
* will be ignored.
*
* NOTE: this method never tries to auto-detect format, since actual
* (data-format specific) parser is given.
*/
@SuppressWarnings("unchecked")
@Override
public T readTree(JsonParser jp)
throws IOException, JsonProcessingException
{
return (T) _bindAsTree(jp);
}
@Override
public void writeTree(JsonGenerator jgen, TreeNode rootNode) {
throw new UnsupportedOperationException();
}
/*
/**********************************************************
/* Deserialization methods; others similar to what ObjectMapper has
/**********************************************************
*/
/**
* Method that binds content read from given input source,
* using configuration of this reader.
* Value return is either newly constructed, or root value that
* was specified with {@link #withValueToUpdate(Object)}.
*/
@SuppressWarnings("unchecked")
public T readValue(InputStream src)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
return (T) _detectBindAndClose(_dataFormatReaders.findFormat(src), false);
}
return (T) _bindAndClose(_parserFactory.createParser(src), _valueToUpdate);
}
/**
* Method that binds content read from given input source,
* using configuration of this reader.
* Value return is either newly constructed, or root value that
* was specified with {@link #withValueToUpdate(Object)}.
*/
@SuppressWarnings("unchecked")
public T readValue(Reader src)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(src);
}
return (T) _bindAndClose(_parserFactory.createParser(src), _valueToUpdate);
}
/**
* Method that binds content read from given JSON string,
* using configuration of this reader.
* Value return is either newly constructed, or root value that
* was specified with {@link #withValueToUpdate(Object)}.
*/
@SuppressWarnings("unchecked")
public T readValue(String src)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(src);
}
return (T) _bindAndClose(_parserFactory.createParser(src), _valueToUpdate);
}
/**
* Method that binds content read from given byte array,
* using configuration of this reader.
* Value return is either newly constructed, or root value that
* was specified with {@link #withValueToUpdate(Object)}.
*/
@SuppressWarnings("unchecked")
public T readValue(byte[] src)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
return (T) _detectBindAndClose(src, 0, src.length);
}
return (T) _bindAndClose(_parserFactory.createParser(src), _valueToUpdate);
}
/**
* Method that binds content read from given byte array,
* using configuration of this reader.
* Value return is either newly constructed, or root value that
* was specified with {@link #withValueToUpdate(Object)}.
*/
@SuppressWarnings("unchecked")
public T readValue(byte[] src, int offset, int length)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
return (T) _detectBindAndClose(src, offset, length);
}
return (T) _bindAndClose(_parserFactory.createParser(src, offset, length), _valueToUpdate);
}
@SuppressWarnings("unchecked")
public T readValue(File src)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
return (T) _detectBindAndClose(_dataFormatReaders.findFormat(_inputStream(src)), true);
}
return (T) _bindAndClose(_parserFactory.createParser(src), _valueToUpdate);
}
/**
* Method that binds content read from given input source,
* using configuration of this reader.
* Value return is either newly constructed, or root value that
* was specified with {@link #withValueToUpdate(Object)}.
*/
@SuppressWarnings("unchecked")
public T readValue(URL src)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
return (T) _detectBindAndClose(_dataFormatReaders.findFormat(_inputStream(src)), true);
}
return (T) _bindAndClose(_parserFactory.createParser(src), _valueToUpdate);
}
/**
* Convenience method for converting results from given JSON tree into given
* value type. Basically short-cut for:
*
* objectReader.readValue(src.traverse())
*
*/
@SuppressWarnings("unchecked")
public T readValue(JsonNode src)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(src);
}
return (T) _bindAndClose(treeAsTokens(src), _valueToUpdate);
}
/**
* Method that reads content from given input source,
* using configuration of this reader, and binds it as JSON Tree.
*
* Note that if an object was specified with a call to
* {@link #withValueToUpdate(Object)}
* it will just be ignored; result is always a newly constructed
* {@link JsonNode} instance.
*/
public JsonNode readTree(InputStream in)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
return _detectBindAndCloseAsTree(in);
}
return _bindAndCloseAsTree(_parserFactory.createParser(in));
}
/**
* Method that reads content from given input source,
* using configuration of this reader, and binds it as JSON Tree.
*
* Note that if an object was specified with a call to
* {@link #withValueToUpdate(Object)}
* it will just be ignored; result is always a newly constructed
* {@link JsonNode} instance.
*/
public JsonNode readTree(Reader r)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(r);
}
return _bindAndCloseAsTree(_parserFactory.createParser(r));
}
/**
* Method that reads content from given JSON input String,
* using configuration of this reader, and binds it as JSON Tree.
*
* Note that if an object was specified with a call to
* {@link #withValueToUpdate(Object)}
* it will just be ignored; result is always a newly constructed
* {@link JsonNode} instance.
*/
public JsonNode readTree(String json)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(json);
}
return _bindAndCloseAsTree(_parserFactory.createParser(json));
}
/*
/**********************************************************
/* Deserialization methods; reading sequence of values
/**********************************************************
*/
/**
* Method for reading sequence of Objects from parser stream.
*
* Sequence can be either root-level "unwrapped" sequence (without surrounding
* JSON array), or a sequence contained in a JSON Array.
* In either case {@link JsonParser} must point to the first token of
* the first element, OR not point to any token (in which case it is advanced
* to the next token). This means, specifically, that for wrapped sequences,
* parser MUST NOT point to the surrounding START_ARRAY
but rather
* to the token following it.
*/
public MappingIterator readValues(JsonParser jp)
throws IOException, JsonProcessingException
{
DeserializationContext ctxt = createDeserializationContext(jp, _config);
// false -> do not close as caller gave parser instance
return new MappingIterator(_valueType, jp, ctxt,
_findRootDeserializer(ctxt, _valueType),
false, _valueToUpdate);
}
/**
* Method for reading sequence of Objects from parser stream.
*
* Sequence can be either wrapped or unwrapped root-level sequence:
* wrapped means that the elements are enclosed in JSON Array;
* and unwrapped that elements are directly accessed at main level.
* Assumption is that iff the first token of the document is
* START_ARRAY
, we have a wrapped sequence; otherwise
* unwrapped. For wrapped sequences, leading START_ARRAY
* is skipped, so that for both cases, underlying {@link JsonParser}
* will point to what is expected to be the first token of the first
* element.
*
* Note that the wrapped vs unwrapped logic means that it is NOT
* possible to use this method for reading an unwrapped sequence
* of elements written as JSON Arrays: to read such sequences, one
* has to use {@link #readValues(JsonParser)}, making sure parser
* points to the first token of the first element (i.e. the second
* START_ARRAY
which is part of the first element).
*/
public MappingIterator readValues(InputStream src)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
return _detectBindAndReadValues(_dataFormatReaders.findFormat(src), false);
}
return _bindAndReadValues(_parserFactory.createParser(src), _valueToUpdate);
}
/**
* Overloaded version of {@link #readValue(InputStream)}.
*/
@SuppressWarnings("resource")
public MappingIterator readValues(Reader src)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(src);
}
JsonParser jp = _parserFactory.createParser(src);
if (_schema != null) {
jp.setSchema(_schema);
}
jp.nextToken();
DeserializationContext ctxt = createDeserializationContext(jp, _config);
return new MappingIterator(_valueType, jp, ctxt,
_findRootDeserializer(ctxt, _valueType), true, _valueToUpdate);
}
/**
* Overloaded version of {@link #readValue(InputStream)}.
*
* @param json String that contains JSON content to parse
*/
@SuppressWarnings("resource")
public MappingIterator readValues(String json)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
_reportUndetectableSource(json);
}
JsonParser jp = _parserFactory.createParser(json);
if (_schema != null) {
jp.setSchema(_schema);
}
jp.nextToken();
DeserializationContext ctxt = createDeserializationContext(jp, _config);
return new MappingIterator(_valueType, jp, ctxt,
_findRootDeserializer(ctxt, _valueType), true, _valueToUpdate);
}
/**
* Overloaded version of {@link #readValue(InputStream)}.
*/
public MappingIterator readValues(byte[] src, int offset, int length)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
return _detectBindAndReadValues(_dataFormatReaders.findFormat(src, offset, length), false);
}
return _bindAndReadValues(_parserFactory.createParser(src), _valueToUpdate);
}
/**
* Overloaded version of {@link #readValue(InputStream)}.
*/
public final MappingIterator readValues(byte[] src)
throws IOException, JsonProcessingException {
return readValues(src, 0, src.length);
}
/**
* Overloaded version of {@link #readValue(InputStream)}.
*/
public MappingIterator readValues(File src)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
return _detectBindAndReadValues(
_dataFormatReaders.findFormat(_inputStream(src)), false);
}
return _bindAndReadValues(_parserFactory.createParser(src), _valueToUpdate);
}
/**
* Overloaded version of {@link #readValue(InputStream)}.
*
* @param src URL to read to access JSON content to parse.
*/
public MappingIterator readValues(URL src)
throws IOException, JsonProcessingException
{
if (_dataFormatReaders != null) {
return _detectBindAndReadValues(
_dataFormatReaders.findFormat(_inputStream(src)), true);
}
return _bindAndReadValues(_parserFactory.createParser(src), _valueToUpdate);
}
/*
/**********************************************************
/* Implementation of rest of ObjectCodec methods
/**********************************************************
*/
@Override
public T treeToValue(TreeNode n, Class valueType)
throws JsonProcessingException
{
try {
return readValue(treeAsTokens(n), valueType);
} catch (JsonProcessingException e) {
throw e;
} catch (IOException e) { // should not occur, no real i/o...
throw new IllegalArgumentException(e.getMessage(), e);
}
}
@Override
public void writeValue(JsonGenerator jgen, Object value) throws IOException, JsonProcessingException
{
throw new UnsupportedOperationException("Not implemented for ObjectReader");
}
/*
/**********************************************************
/* Helper methods, data-binding
/**********************************************************
*/
/**
* Actual implementation of value reading+binding operation.
*/
protected Object _bind(JsonParser jp, Object valueToUpdate)
throws IOException, JsonParseException, JsonMappingException
{
/* First: may need to read the next token, to initialize state (either
* before first read from parser, or after previous token has been cleared)
*/
Object result;
JsonToken t = _initForReading(jp);
if (t == JsonToken.VALUE_NULL) {
if (valueToUpdate == null) {
DeserializationContext ctxt = createDeserializationContext(jp, _config);
result = _findRootDeserializer(ctxt, _valueType).getNullValue();
} else {
result = valueToUpdate;
}
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = valueToUpdate;
} else { // pointing to event other than null
DeserializationContext ctxt = createDeserializationContext(jp, _config);
JsonDeserializer deser = _findRootDeserializer(ctxt, _valueType);
if (_unwrapRoot) {
result = _unwrapAndDeserialize(jp, ctxt, _valueType, deser);
} else {
if (valueToUpdate == null) {
result = deser.deserialize(jp, ctxt);
} else {
deser.deserialize(jp, ctxt, valueToUpdate);
result = valueToUpdate;
}
}
}
// Need to consume the token too
jp.clearCurrentToken();
return result;
}
protected Object _bindAndClose(JsonParser jp, Object valueToUpdate)
throws IOException, JsonParseException, JsonMappingException
{
if (_schema != null) {
jp.setSchema(_schema);
}
try {
Object result;
JsonToken t = _initForReading(jp);
if (t == JsonToken.VALUE_NULL) {
if (valueToUpdate == null) {
DeserializationContext ctxt = createDeserializationContext(jp, _config);
result = _findRootDeserializer(ctxt, _valueType).getNullValue();
} else {
result = valueToUpdate;
}
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = valueToUpdate;
} else {
DeserializationContext ctxt = createDeserializationContext(jp, _config);
JsonDeserializer deser = _findRootDeserializer(ctxt, _valueType);
if (_unwrapRoot) {
result = _unwrapAndDeserialize(jp, ctxt, _valueType, deser);
} else {
if (valueToUpdate == null) {
result = deser.deserialize(jp, ctxt);
} else {
deser.deserialize(jp, ctxt, valueToUpdate);
result = valueToUpdate;
}
}
}
return result;
} finally {
try {
jp.close();
} catch (IOException ioe) { }
}
}
protected JsonNode _bindAsTree(JsonParser jp)
throws IOException, JsonParseException, JsonMappingException
{
JsonNode result;
JsonToken t = _initForReading(jp);
if (t == JsonToken.VALUE_NULL || t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = NullNode.instance;
} else {
DeserializationContext ctxt = createDeserializationContext(jp, _config);
JsonDeserializer deser = _findRootDeserializer(ctxt, JSON_NODE_TYPE);
if (_unwrapRoot) {
result = (JsonNode) _unwrapAndDeserialize(jp, ctxt, JSON_NODE_TYPE, deser);
} else {
result = (JsonNode) deser.deserialize(jp, ctxt);
}
}
// Need to consume the token too
jp.clearCurrentToken();
return result;
}
protected JsonNode _bindAndCloseAsTree(JsonParser jp)
throws IOException, JsonParseException, JsonMappingException
{
if (_schema != null) {
jp.setSchema(_schema);
}
try {
return _bindAsTree(jp);
} finally {
try {
jp.close();
} catch (IOException ioe) { }
}
}
/**
* @since 2.1
*/
protected MappingIterator _bindAndReadValues(JsonParser p,
Object valueToUpdate)
throws IOException, JsonProcessingException
{
if (_schema != null) {
p.setSchema(_schema);
}
p.nextToken();
DeserializationContext ctxt = createDeserializationContext(p, _config);
return new MappingIterator(_valueType, p, ctxt,
_findRootDeserializer(ctxt, _valueType),
true, _valueToUpdate);
}
protected static JsonToken _initForReading(JsonParser jp)
throws IOException, JsonParseException, JsonMappingException
{
/* First: must point to a token; if not pointing to one, advance.
* This occurs before first read from JsonParser, as well as
* after clearing of current token.
*/
JsonToken t = jp.getCurrentToken();
if (t == null) { // and then we must get something...
t = jp.nextToken();
if (t == null) {
/* [JACKSON-546] Throw mapping exception, since it's failure to map,
* not an actual parsing problem
*/
throw JsonMappingException.from(jp, "No content to map due to end-of-input");
}
}
return t;
}
/**
* Method called to locate deserializer for the passed root-level value.
*/
protected JsonDeserializer _findRootDeserializer(DeserializationContext ctxt,
JavaType valueType)
throws JsonMappingException
{
if (_rootDeserializer != null) {
return _rootDeserializer;
}
// Sanity check: must have actual type...
if (valueType == null) {
throw new JsonMappingException("No value type configured for ObjectReader");
}
// First: have we already seen it?
JsonDeserializer deser = _rootDeserializers.get(valueType);
if (deser != null) {
return deser;
}
// Nope: need to ask provider to resolve it
deser = ctxt.findRootValueDeserializer(valueType);
if (deser == null) { // can this happen?
throw new JsonMappingException("Can not find a deserializer for type "+valueType);
}
_rootDeserializers.put(valueType, deser);
return deser;
}
/**
* Method called to locate deserializer ahead of time, if permitted
* by configuration. Method also is NOT to throw an exception if
* access fails.
*/
protected JsonDeserializer _prefetchRootDeserializer(
DeserializationConfig config, JavaType valueType)
{
if (valueType == null || !_config.isEnabled(DeserializationFeature.EAGER_DESERIALIZER_FETCH)) {
return null;
}
// already cached?
JsonDeserializer deser = _rootDeserializers.get(valueType);
if (deser == null) {
try {
// If not, need to resolve; for which we need a temporary context as well:
DeserializationContext ctxt = createDeserializationContext(null, _config);
deser = ctxt.findRootValueDeserializer(valueType);
if (deser != null) {
_rootDeserializers.put(valueType, deser);
}
return deser;
} catch (JsonProcessingException e) {
// need to swallow?
}
}
return deser;
}
protected Object _unwrapAndDeserialize(JsonParser jp, DeserializationContext ctxt,
JavaType rootType, JsonDeserializer deser)
throws IOException, JsonParseException, JsonMappingException
{
String expName = _config.getRootName();
if (expName == null) {
PropertyName pname = _rootNames.findRootName(rootType, _config);
expName = pname.getSimpleName();
}
if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
throw JsonMappingException.from(jp, "Current token not START_OBJECT (needed to unwrap root name '"
+expName+"'), but "+jp.getCurrentToken());
}
if (jp.nextToken() != JsonToken.FIELD_NAME) {
throw JsonMappingException.from(jp, "Current token not FIELD_NAME (to contain expected root name '"
+expName+"'), but "+jp.getCurrentToken());
}
String actualName = jp.getCurrentName();
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;
if (_valueToUpdate == null) {
result = deser.deserialize(jp, ctxt);
} else {
deser.deserialize(jp, ctxt, _valueToUpdate);
result = _valueToUpdate;
}
// 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 '"
+expName+"'), but "+jp.getCurrentToken());
}
return result;
}
/*
/**********************************************************
/* Internal methods, format auto-detection (since 2.1)
/**********************************************************
*/
@SuppressWarnings("resource")
protected Object _detectBindAndClose(byte[] src, int offset, int length) throws IOException
{
DataFormatReaders.Match match = _dataFormatReaders.findFormat(src, offset, length);
if (!match.hasMatch()) {
_reportUnkownFormat(_dataFormatReaders, match);
}
JsonParser jp = match.createParserWithMatch();
return match.getReader()._bindAndClose(jp, _valueToUpdate);
}
@SuppressWarnings("resource")
protected Object _detectBindAndClose(DataFormatReaders.Match match, boolean forceClosing)
throws IOException
{
if (!match.hasMatch()) {
_reportUnkownFormat(_dataFormatReaders, match);
}
JsonParser p = match.createParserWithMatch();
// One more thing: we Own the input stream now; and while it's
// not super clean way to do it, we must ensure closure so:
if (forceClosing) {
p.enable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
}
// important: use matching ObjectReader (may not be 'this')
return match.getReader()._bindAndClose(p, _valueToUpdate);
}
@SuppressWarnings("resource")
protected MappingIterator _detectBindAndReadValues(DataFormatReaders.Match match, boolean forceClosing)
throws IOException, JsonProcessingException
{
if (!match.hasMatch()) {
_reportUnkownFormat(_dataFormatReaders, match);
}
JsonParser p = match.createParserWithMatch();
// One more thing: we Own the input stream now; and while it's
// not super clean way to do it, we must ensure closure so:
if (forceClosing) {
p.enable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
}
// important: use matching ObjectReader (may not be 'this')
return match.getReader()._bindAndReadValues(p, _valueToUpdate);
}
@SuppressWarnings("resource")
protected JsonNode _detectBindAndCloseAsTree(InputStream in) throws IOException
{
DataFormatReaders.Match match = _dataFormatReaders.findFormat(in);
if (!match.hasMatch()) {
_reportUnkownFormat(_dataFormatReaders, match);
}
JsonParser p = match.createParserWithMatch();
p.enable(JsonParser.Feature.AUTO_CLOSE_SOURCE);
return match.getReader()._bindAndCloseAsTree(p);
}
/**
* Method called to indicate that format detection failed to detect format
* of given input
*/
protected void _reportUnkownFormat(DataFormatReaders detector, DataFormatReaders.Match match) throws JsonProcessingException
{
throw new JsonParseException("Can not detect format from input, does not look like any of detectable formats "
+detector.toString(),
JsonLocation.NA);
}
/*
/**********************************************************
/* Internal methods, other
/**********************************************************
*/
/**
* @since 2.2
*/
protected void _verifySchemaType(FormatSchema schema)
{
if (schema != null) {
if (!_parserFactory.canUseSchema(schema)) {
throw new IllegalArgumentException("Can not use FormatSchema of type "+schema.getClass().getName()
+" for format "+_parserFactory.getFormatName());
}
}
}
/**
* Internal helper method called to create an instance of {@link DeserializationContext}
* for deserializing a single root value.
* Can be overridden if a custom context is needed.
*/
protected DefaultDeserializationContext createDeserializationContext(JsonParser jp,
DeserializationConfig cfg) {
// 04-Jan-2010, tatu: we do actually need the provider too... (for polymorphic deser)
return _context.createInstance(cfg, jp, _injectableValues);
}
protected ObjectReader _with(DeserializationConfig newConfig) {
if (newConfig == _config) {
return this;
}
if (_dataFormatReaders != null) {
return new ObjectReader(this, newConfig)
.withFormatDetection(_dataFormatReaders.with(newConfig));
}
return new ObjectReader(this, newConfig);
}
protected void _reportUndetectableSource(Object src) throws JsonProcessingException
{
throw new JsonParseException("Can not use source of type "
+src.getClass().getName()+" with format auto-detection: must be byte- not char-based",
JsonLocation.NA);
}
protected InputStream _inputStream(URL src) throws IOException {
return src.openStream();
}
protected InputStream _inputStream(File f) throws IOException {
return new FileInputStream(f);
}
}