| 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); |
| } |
| } |