blob: ca620335737752c062bfde42a468863b4f16a9b5 [file] [log] [blame]
package com.fasterxml.jackson.databind.deser.std;
import java.io.IOException;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.node.*;
import com.fasterxml.jackson.databind.util.RawValue;
/**
* Deserializer that can build instances of {@link JsonNode} from any
* JSON content, using appropriate {@link JsonNode} type.
*/
@SuppressWarnings("serial")
public class JsonNodeDeserializer
extends BaseNodeDeserializer<JsonNode>
{
/**
* Singleton instance of generic deserializer for {@link JsonNode}.
* Only used for types other than JSON Object and Array.
*/
private final static JsonNodeDeserializer instance = new JsonNodeDeserializer();
protected JsonNodeDeserializer() {
// `null` means that explicit "merge" is honored and may or may not work, but
// that per-type and global defaults do not enable merging. This because
// some node types (Object, Array) do support, others don't.
super(JsonNode.class, null);
}
/**
* Factory method for accessing deserializer for specific node type
*/
public static JsonDeserializer<? extends JsonNode> getDeserializer(Class<?> nodeClass)
{
if (nodeClass == ObjectNode.class) {
return ObjectDeserializer.getInstance();
}
if (nodeClass == ArrayNode.class) {
return ArrayDeserializer.getInstance();
}
// For others, generic one works fine
return instance;
}
/*
/**********************************************************
/* Actual deserializer implementations
/**********************************************************
*/
@Override
public JsonNode getNullValue(DeserializationContext ctxt) {
return NullNode.getInstance();
}
/**
* Implementation that will produce types of any JSON nodes; not just one
* deserializer is registered to handle (in case of more specialized handler).
* Overridden by typed sub-classes for more thorough checking
*/
@Override
public JsonNode deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
switch (p.getCurrentTokenId()) {
case JsonTokenId.ID_START_OBJECT:
return deserializeObject(p, ctxt, ctxt.getNodeFactory());
case JsonTokenId.ID_START_ARRAY:
return deserializeArray(p, ctxt, ctxt.getNodeFactory());
default:
}
return deserializeAny(p, ctxt, ctxt.getNodeFactory());
}
/*
/**********************************************************
/* Specific instances for more accurate types
/**********************************************************
*/
final static class ObjectDeserializer
extends BaseNodeDeserializer<ObjectNode>
{
private static final long serialVersionUID = 1L;
protected final static ObjectDeserializer _instance = new ObjectDeserializer();
protected ObjectDeserializer() { super(ObjectNode.class, true); }
public static ObjectDeserializer getInstance() { return _instance; }
@Override
public ObjectNode deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
if (p.isExpectedStartObjectToken()) {
return deserializeObject(p, ctxt, ctxt.getNodeFactory());
}
if (p.hasToken(JsonToken.FIELD_NAME)) {
return deserializeObjectAtName(p, ctxt, ctxt.getNodeFactory());
}
// 23-Sep-2015, tatu: Ugh. We may also be given END_OBJECT (similar to FIELD_NAME),
// if caller has advanced to the first token of Object, but for empty Object
if (p.hasToken(JsonToken.END_OBJECT)) {
return ctxt.getNodeFactory().objectNode();
}
return (ObjectNode) ctxt.handleUnexpectedToken(ObjectNode.class, p);
}
/**
* Variant needed to support both root-level `updateValue()` and merging.
*
* @since 2.9
*/
@Override
public ObjectNode deserialize(JsonParser p, DeserializationContext ctxt,
ObjectNode node) throws IOException
{
if (p.isExpectedStartObjectToken() || p.hasToken(JsonToken.FIELD_NAME)) {
return (ObjectNode) updateObject(p, ctxt, (ObjectNode) node);
}
return (ObjectNode) ctxt.handleUnexpectedToken(ObjectNode.class, p);
}
}
final static class ArrayDeserializer
extends BaseNodeDeserializer<ArrayNode>
{
private static final long serialVersionUID = 1L;
protected final static ArrayDeserializer _instance = new ArrayDeserializer();
protected ArrayDeserializer() { super(ArrayNode.class, true); }
public static ArrayDeserializer getInstance() { return _instance; }
@Override
public ArrayNode deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
if (p.isExpectedStartArrayToken()) {
return deserializeArray(p, ctxt, ctxt.getNodeFactory());
}
return (ArrayNode) ctxt.handleUnexpectedToken(ArrayNode.class, p);
}
/**
* Variant needed to support both root-level `updateValue()` and merging.
*
* @since 2.9
*/
@Override
public ArrayNode deserialize(JsonParser p, DeserializationContext ctxt,
ArrayNode node) throws IOException
{
if (p.isExpectedStartArrayToken()) {
return (ArrayNode) updateArray(p, ctxt, (ArrayNode) node);
}
return (ArrayNode) ctxt.handleUnexpectedToken(ArrayNode.class, p);
}
}
}
/**
* Base class for all actual {@link JsonNode} deserializer
* implementations
*/
@SuppressWarnings("serial")
abstract class BaseNodeDeserializer<T extends JsonNode>
extends StdDeserializer<T>
{
protected final Boolean _supportsUpdates;
public BaseNodeDeserializer(Class<T> vc, Boolean supportsUpdates) {
super(vc);
_supportsUpdates = supportsUpdates;
}
@Override
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer)
throws IOException
{
// Output can be as JSON Object, Array or scalar: no way to know a priori:
return typeDeserializer.deserializeTypedFromAny(p, ctxt);
}
/* 07-Nov-2014, tatu: When investigating [databind#604], realized that it makes
* sense to also mark this is cachable, since lookup not exactly free, and
* since it's not uncommon to "read anything"
*/
@Override
public boolean isCachable() { return true; }
@Override // since 2.9
public Boolean supportsUpdate(DeserializationConfig config) {
return _supportsUpdates;
}
/*
/**********************************************************
/* Overridable methods
/**********************************************************
*/
/**
* Method called when there is a duplicate value for a field.
* By default we don't care, and the last value is used.
* Can be overridden to provide alternate handling, such as throwing
* an exception, or choosing different strategy for combining values
* or choosing which one to keep.
*
* @param fieldName Name of the field for which duplicate value was found
* @param objectNode Object node that contains values
* @param oldValue Value that existed for the object node before newValue
* was added
* @param newValue Newly added value just added to the object node
*/
protected void _handleDuplicateField(JsonParser p, DeserializationContext ctxt,
JsonNodeFactory nodeFactory,
String fieldName, ObjectNode objectNode,
JsonNode oldValue, JsonNode newValue)
throws JsonProcessingException
{
// [databind#237]: Report an error if asked to do so:
if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY)) {
ctxt.reportInputMismatch(JsonNode.class,
"Duplicate field '%s' for ObjectNode: not allowed when FAIL_ON_READING_DUP_TREE_KEY enabled",
fieldName);
}
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
/**
* Method called to deserialize Object node instance when there is no existing
* node to modify.
*/
protected final ObjectNode deserializeObject(JsonParser p, DeserializationContext ctxt,
final JsonNodeFactory nodeFactory) throws IOException
{
final ObjectNode node = nodeFactory.objectNode();
String key = p.nextFieldName();
for (; key != null; key = p.nextFieldName()) {
JsonNode value;
JsonToken t = p.nextToken();
if (t == null) { // can this ever occur?
t = JsonToken.NOT_AVAILABLE; // can this ever occur?
}
switch (t.id()) {
case JsonTokenId.ID_START_OBJECT:
value = deserializeObject(p, ctxt, nodeFactory);
break;
case JsonTokenId.ID_START_ARRAY:
value = deserializeArray(p, ctxt, nodeFactory);
break;
case JsonTokenId.ID_EMBEDDED_OBJECT:
value = _fromEmbedded(p, ctxt, nodeFactory);
break;
case JsonTokenId.ID_STRING:
value = nodeFactory.textNode(p.getText());
break;
case JsonTokenId.ID_NUMBER_INT:
value = _fromInt(p, ctxt, nodeFactory);
break;
case JsonTokenId.ID_TRUE:
value = nodeFactory.booleanNode(true);
break;
case JsonTokenId.ID_FALSE:
value = nodeFactory.booleanNode(false);
break;
case JsonTokenId.ID_NULL:
value = nodeFactory.nullNode();
break;
default:
value = deserializeAny(p, ctxt, nodeFactory);
}
JsonNode old = node.replace(key, value);
if (old != null) {
_handleDuplicateField(p, ctxt, nodeFactory,
key, node, old, value);
}
}
return node;
}
/**
* Alternate deserialization method used when parser already points to first
* FIELD_NAME and not START_OBJECT.
*
* @since 2.9
*/
protected final ObjectNode deserializeObjectAtName(JsonParser p, DeserializationContext ctxt,
final JsonNodeFactory nodeFactory) throws IOException
{
final ObjectNode node = nodeFactory.objectNode();
String key = p.getCurrentName();
for (; key != null; key = p.nextFieldName()) {
JsonNode value;
JsonToken t = p.nextToken();
if (t == null) { // can this ever occur?
t = JsonToken.NOT_AVAILABLE; // can this ever occur?
}
switch (t.id()) {
case JsonTokenId.ID_START_OBJECT:
value = deserializeObject(p, ctxt, nodeFactory);
break;
case JsonTokenId.ID_START_ARRAY:
value = deserializeArray(p, ctxt, nodeFactory);
break;
case JsonTokenId.ID_EMBEDDED_OBJECT:
value = _fromEmbedded(p, ctxt, nodeFactory);
break;
case JsonTokenId.ID_STRING:
value = nodeFactory.textNode(p.getText());
break;
case JsonTokenId.ID_NUMBER_INT:
value = _fromInt(p, ctxt, nodeFactory);
break;
case JsonTokenId.ID_TRUE:
value = nodeFactory.booleanNode(true);
break;
case JsonTokenId.ID_FALSE:
value = nodeFactory.booleanNode(false);
break;
case JsonTokenId.ID_NULL:
value = nodeFactory.nullNode();
break;
default:
value = deserializeAny(p, ctxt, nodeFactory);
}
JsonNode old = node.replace(key, value);
if (old != null) {
_handleDuplicateField(p, ctxt, nodeFactory,
key, node, old, value);
}
}
return node;
}
/**
* Alternate deserialization method that is to update existing {@link ObjectNode}
* if possible.
*
* @since 2.9
*/
protected final JsonNode updateObject(JsonParser p, DeserializationContext ctxt,
final ObjectNode node) throws IOException
{
String key;
if (p.isExpectedStartObjectToken()) {
key = p.nextFieldName();
} else {
if (!p.hasToken(JsonToken.FIELD_NAME)) {
return deserialize(p, ctxt);
}
key = p.getCurrentName();
}
for (; key != null; key = p.nextFieldName()) {
// If not, fall through to regular handling
JsonToken t = p.nextToken();
// First: see if we can merge things:
JsonNode old = node.get(key);
if (old != null) {
if (old instanceof ObjectNode) {
JsonNode newValue = updateObject(p, ctxt, (ObjectNode) old);
if (newValue != old) {
node.set(key, newValue);
}
continue;
}
if (old instanceof ArrayNode) {
JsonNode newValue = updateArray(p, ctxt, (ArrayNode) old);
if (newValue != old) {
node.set(key, newValue);
}
continue;
}
}
if (t == null) { // can this ever occur?
t = JsonToken.NOT_AVAILABLE;
}
JsonNode value;
JsonNodeFactory nodeFactory = ctxt.getNodeFactory();
switch (t.id()) {
case JsonTokenId.ID_START_OBJECT:
value = deserializeObject(p, ctxt, nodeFactory);
break;
case JsonTokenId.ID_START_ARRAY:
value = deserializeArray(p, ctxt, nodeFactory);
break;
case JsonTokenId.ID_EMBEDDED_OBJECT:
value = _fromEmbedded(p, ctxt, nodeFactory);
break;
case JsonTokenId.ID_STRING:
value = nodeFactory.textNode(p.getText());
break;
case JsonTokenId.ID_NUMBER_INT:
value = _fromInt(p, ctxt, nodeFactory);
break;
case JsonTokenId.ID_TRUE:
value = nodeFactory.booleanNode(true);
break;
case JsonTokenId.ID_FALSE:
value = nodeFactory.booleanNode(false);
break;
case JsonTokenId.ID_NULL:
value = nodeFactory.nullNode();
break;
default:
value = deserializeAny(p, ctxt, nodeFactory);
}
if (old != null) {
_handleDuplicateField(p, ctxt, nodeFactory,
key, node, old, value);
}
node.set(key, value);
}
return node;
}
protected final ArrayNode deserializeArray(JsonParser p, DeserializationContext ctxt,
final JsonNodeFactory nodeFactory) throws IOException
{
ArrayNode node = nodeFactory.arrayNode();
while (true) {
JsonToken t = p.nextToken();
switch (t.id()) {
case JsonTokenId.ID_START_OBJECT:
node.add(deserializeObject(p, ctxt, nodeFactory));
break;
case JsonTokenId.ID_START_ARRAY:
node.add(deserializeArray(p, ctxt, nodeFactory));
break;
case JsonTokenId.ID_END_ARRAY:
return node;
case JsonTokenId.ID_EMBEDDED_OBJECT:
node.add(_fromEmbedded(p, ctxt, nodeFactory));
break;
case JsonTokenId.ID_STRING:
node.add(nodeFactory.textNode(p.getText()));
break;
case JsonTokenId.ID_NUMBER_INT:
node.add(_fromInt(p, ctxt, nodeFactory));
break;
case JsonTokenId.ID_TRUE:
node.add(nodeFactory.booleanNode(true));
break;
case JsonTokenId.ID_FALSE:
node.add(nodeFactory.booleanNode(false));
break;
case JsonTokenId.ID_NULL:
node.add(nodeFactory.nullNode());
break;
default:
node.add(deserializeAny(p, ctxt, nodeFactory));
break;
}
}
}
/**
* Alternate deserialization method that is to update existing {@link ObjectNode}
* if possible.
*
* @since 2.9
*/
protected final JsonNode updateArray(JsonParser p, DeserializationContext ctxt,
final ArrayNode node) throws IOException
{
final JsonNodeFactory nodeFactory = ctxt.getNodeFactory();
while (true) {
JsonToken t = p.nextToken();
switch (t.id()) {
case JsonTokenId.ID_START_OBJECT:
node.add(deserializeObject(p, ctxt, nodeFactory));
break;
case JsonTokenId.ID_START_ARRAY:
node.add(deserializeArray(p, ctxt, nodeFactory));
break;
case JsonTokenId.ID_END_ARRAY:
return node;
case JsonTokenId.ID_EMBEDDED_OBJECT:
node.add(_fromEmbedded(p, ctxt, nodeFactory));
break;
case JsonTokenId.ID_STRING:
node.add(nodeFactory.textNode(p.getText()));
break;
case JsonTokenId.ID_NUMBER_INT:
node.add(_fromInt(p, ctxt, nodeFactory));
break;
case JsonTokenId.ID_TRUE:
node.add(nodeFactory.booleanNode(true));
break;
case JsonTokenId.ID_FALSE:
node.add(nodeFactory.booleanNode(false));
break;
case JsonTokenId.ID_NULL:
node.add(nodeFactory.nullNode());
break;
default:
node.add(deserializeAny(p, ctxt, nodeFactory));
break;
}
}
}
protected final JsonNode deserializeAny(JsonParser p, DeserializationContext ctxt,
final JsonNodeFactory nodeFactory) throws IOException
{
switch (p.getCurrentTokenId()) {
case JsonTokenId.ID_END_OBJECT: // for empty JSON Objects we may point to this?
return nodeFactory.objectNode();
case JsonTokenId.ID_FIELD_NAME:
return deserializeObjectAtName(p, ctxt, nodeFactory);
case JsonTokenId.ID_EMBEDDED_OBJECT:
return _fromEmbedded(p, ctxt, nodeFactory);
case JsonTokenId.ID_STRING:
return nodeFactory.textNode(p.getText());
case JsonTokenId.ID_NUMBER_INT:
return _fromInt(p, ctxt, nodeFactory);
case JsonTokenId.ID_NUMBER_FLOAT:
return _fromFloat(p, ctxt, nodeFactory);
case JsonTokenId.ID_TRUE:
return nodeFactory.booleanNode(true);
case JsonTokenId.ID_FALSE:
return nodeFactory.booleanNode(false);
case JsonTokenId.ID_NULL:
return nodeFactory.nullNode();
/* Caller checks for these, should not get here ever
case JsonTokenId.ID_START_OBJECT:
return deserializeObject(p, ctxt, nodeFactory);
case JsonTokenId.ID_START_ARRAY:
return deserializeArray(p, ctxt, nodeFactory);
*/
// These states can not be mapped; input stream is
// off by an event or two
//case END_OBJECT:
//case END_ARRAY:
default:
}
return (JsonNode) ctxt.handleUnexpectedToken(handledType(), p);
}
protected final JsonNode _fromInt(JsonParser p, DeserializationContext ctxt,
JsonNodeFactory nodeFactory) throws IOException
{
JsonParser.NumberType nt;
int feats = ctxt.getDeserializationFeatures();
if ((feats & F_MASK_INT_COERCIONS) != 0) {
if (DeserializationFeature.USE_BIG_INTEGER_FOR_INTS.enabledIn(feats)) {
nt = JsonParser.NumberType.BIG_INTEGER;
} else if (DeserializationFeature.USE_LONG_FOR_INTS.enabledIn(feats)) {
nt = JsonParser.NumberType.LONG;
} else {
nt = p.getNumberType();
}
} else {
nt = p.getNumberType();
}
if (nt == JsonParser.NumberType.INT) {
return nodeFactory.numberNode(p.getIntValue());
}
if (nt == JsonParser.NumberType.LONG) {
return nodeFactory.numberNode(p.getLongValue());
}
return nodeFactory.numberNode(p.getBigIntegerValue());
}
protected final JsonNode _fromFloat(JsonParser p, DeserializationContext ctxt,
final JsonNodeFactory nodeFactory) throws IOException
{
JsonParser.NumberType nt = p.getNumberType();
if (nt == JsonParser.NumberType.BIG_DECIMAL) {
return nodeFactory.numberNode(p.getDecimalValue());
}
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
// 20-May-2016, tatu: As per [databind#1028], need to be careful
// (note: JDK 1.8 would have `Double.isFinite()`)
if (p.isNaN()) {
return nodeFactory.numberNode(p.getDoubleValue());
}
return nodeFactory.numberNode(p.getDecimalValue());
}
if (nt == JsonParser.NumberType.FLOAT) {
return nodeFactory.numberNode(p.getFloatValue());
}
return nodeFactory.numberNode(p.getDoubleValue());
}
protected final JsonNode _fromEmbedded(JsonParser p, DeserializationContext ctxt,
JsonNodeFactory nodeFactory) throws IOException
{
Object ob = p.getEmbeddedObject();
if (ob == null) { // should this occur?
return nodeFactory.nullNode();
}
Class<?> type = ob.getClass();
if (type == byte[].class) { // most common special case
return nodeFactory.binaryNode((byte[]) ob);
}
// [databind#743]: Don't forget RawValue
if (ob instanceof RawValue) {
return nodeFactory.rawValueNode((RawValue) ob);
}
if (ob instanceof JsonNode) {
// [databind#433]: but could also be a JsonNode hiding in there!
return (JsonNode) ob;
}
// any other special handling needed?
return nodeFactory.pojoNode(ob);
}
}