package com.fasterxml.jackson.databind.node;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
/**
* Node that maps to JSON Object structures in JSON content.
*/
public class ObjectNode
extends ContainerNode<ObjectNode>
{
// note: until 2.1, was explicitly `LinkedHashMap`
protected Map<String, JsonNode> _children = null;
public ObjectNode(JsonNodeFactory nc) { super(nc); }
protected ObjectNode(JsonNodeFactory nc, Map<String, JsonNode> children) {
super(nc);
_children = children;
}
/* Question: should this delegate to `JsonNodeFactory`? It does not absolutely
* have to, as long as sub-types override the method but...
*/
// note: co-variant for type safety
@SuppressWarnings("unchecked")
@Override
public ObjectNode deepCopy()
{
/* 28-Sep-2012, tatu: Sub-classes really should override this method to
* produce compliant copies.
*/
if (getClass() != ObjectNode.class) {
throw new IllegalStateException("ObjectNode subtype ("+getClass().getName()+" does not override deepCopy(), needs to");
}
return _defaultDeepCopy();
}
/**
* Default implementation for 'deepCopy()': can be delegated to by sub-classes
* if necessary; but usually isn't.
*/
protected ObjectNode _defaultDeepCopy()
{
if (_children == null) {
return new ObjectNode(_nodeFactory);
}
final int len = _children.size();
Map<String, JsonNode> newKids = _createMap(Math.max(4, len));
for (Map.Entry<String, JsonNode> entry : _children.entrySet()) {
newKids.put(entry.getKey(), entry.getValue().deepCopy());
}
return new ObjectNode(_nodeFactory, newKids);
}
/*
/**********************************************************
/* Implementation of core JsonNode API
/**********************************************************
*/
@Override public JsonToken asToken() { return JsonToken.START_OBJECT; }
@Override
public boolean isObject() { return true; }
@Override
public int size() {
return (_children == null) ? 0 : _children.size();
}
@Override
public Iterator<JsonNode> elements()
{
return (_children == null) ? NoNodesIterator.instance() : _children.values().iterator();
}
@Override
public JsonNode get(int index) { return null; }
@Override
public JsonNode get(String fieldName)
{
if (_children != null) {
return _children.get(fieldName);
}
return null;
}
@Override
public Iterator<String> fieldNames() {
return (_children == null) ? NoStringsIterator.instance() : _children.keySet().iterator();
}
@Override
public JsonNode path(int index)
{
return MissingNode.getInstance();
}
@Override
public JsonNode path(String fieldName)
{
if (_children != null) {
JsonNode n = _children.get(fieldName);
if (n != null) {
return n;
}
}
return MissingNode.getInstance();
}
/**
* Method to use for accessing all fields (with both names
* and values) of this JSON Object.
*/
@Override
public Iterator<Map.Entry<String, JsonNode>> fields()
{
if (_children == null) {
return NoFieldsIterator.instance;
}
return _children.entrySet().iterator();
}
@Override
public ObjectNode with(String propertyName)
{
if (_children == null) {
_children = _createMap();
} else {
JsonNode n = _children.get(propertyName);
if (n != null) {
if (n instanceof ObjectNode) {
return (ObjectNode) n;
}
throw new UnsupportedOperationException("Property '"+propertyName
+"' has value that is not of type ObjectNode (but "
+n.getClass().getName()+")");
}
}
ObjectNode result = objectNode();
_children.put(propertyName, result);
return result;
}
@Override
public ArrayNode withArray(String propertyName)
{
if (_children == null) {
_children = _createMap();
} else {
JsonNode n = _children.get(propertyName);
if (n != null) {
if (n instanceof ArrayNode) {
return (ArrayNode) n;
}
throw new UnsupportedOperationException("Property '"+propertyName
+"' has value that is not of type ArrayNode (but "
+n.getClass().getName()+")");
}
}
ArrayNode result = arrayNode();
_children.put(propertyName, result);
return result;
}
/*
/**********************************************************
/* Public API, finding value nodes
/**********************************************************
*/
@Override
public JsonNode findValue(String fieldName)
{
if (_children != null) {
for (Map.Entry<String, JsonNode> entry : _children.entrySet()) {
if (fieldName.equals(entry.getKey())) {
return entry.getValue();
}
JsonNode value = entry.getValue().findValue(fieldName);
if (value != null) {
return value;
}
}
}
return null;
}
@Override
public List<JsonNode> findValues(String fieldName, List<JsonNode> foundSoFar)
{
if (_children != null) {
for (Map.Entry<String, JsonNode> entry : _children.entrySet()) {
if (fieldName.equals(entry.getKey())) {
if (foundSoFar == null) {
foundSoFar = new ArrayList<JsonNode>();
}
foundSoFar.add(entry.getValue());
} else { // only add children if parent not added
foundSoFar = entry.getValue().findValues(fieldName, foundSoFar);
}
}
}
return foundSoFar;
}
@Override
public List<String> findValuesAsText(String fieldName, List<String> foundSoFar)
{
if (_children != null) {
for (Map.Entry<String, JsonNode> entry : _children.entrySet()) {
if (fieldName.equals(entry.getKey())) {
if (foundSoFar == null) {
foundSoFar = new ArrayList<String>();
}
foundSoFar.add(entry.getValue().asText());
} else { // only add children if parent not added
foundSoFar = entry.getValue().findValuesAsText(fieldName, foundSoFar);
}
}
}
return foundSoFar;
}
@Override
public ObjectNode findParent(String fieldName)
{
if (_children != null) {
for (Map.Entry<String, JsonNode> entry : _children.entrySet()) {
if (fieldName.equals(entry.getKey())) {
return this;
}
JsonNode value = entry.getValue().findParent(fieldName);
if (value != null) {
return (ObjectNode) value;
}
}
}
return null;
}
@Override
public List<JsonNode> findParents(String fieldName, List<JsonNode> foundSoFar)
{
if (_children != null) {
for (Map.Entry<String, JsonNode> entry : _children.entrySet()) {
if (fieldName.equals(entry.getKey())) {
if (foundSoFar == null) {
foundSoFar = new ArrayList<JsonNode>();
}
foundSoFar.add(this);
} else { // only add children if parent not added
foundSoFar = entry.getValue().findParents(fieldName, foundSoFar);
}
}
}
return foundSoFar;
}
/*
/**********************************************************
/* Public API, serialization
/**********************************************************
*/
/**
* Method that can be called to serialize this node and
* all of its descendants using specified JSON generator.
*/
@Override
public final void serialize(JsonGenerator jg, SerializerProvider provider)
throws IOException, JsonProcessingException
{
jg.writeStartObject();
if (_children != null) {
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
jg.writeFieldName(en.getKey());
/* 17-Feb-2009, tatu: Can we trust that all nodes will always
* extend BaseJsonNode? Or if not, at least implement
* JsonSerializable? Let's start with former, change if
* we must.
*/
((BaseJsonNode) en.getValue()).serialize(jg, provider);
}
}
jg.writeEndObject();
}
@Override
public void serializeWithType(JsonGenerator jg, SerializerProvider provider,
TypeSerializer typeSer)
throws IOException, JsonProcessingException
{
typeSer.writeTypePrefixForObject(this, jg);
if (_children != null) {
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
jg.writeFieldName(en.getKey());
((BaseJsonNode) en.getValue()).serialize(jg, provider);
}
}
typeSer.writeTypeSuffixForObject(this, jg);
}
/*
/**********************************************************
/* Extended ObjectNode API, mutators, since 2.1
/**********************************************************
*/
/**
* Method that will set specified field, replacing old value, if any.
* Note that this is identical to {@link #replace(String, JsonNode)},
* except for return value.
*<p>
* NOTE: added to replace those uses of {@link #put(String, JsonNode)}
* where chaining with 'this' is desired.
*
* @param value to set field to; if null, will be converted
* to a {@link NullNode} first (to remove field entry, call
* {@link #remove} instead)
*
* @return This node after adding/replacing property value (to allow chaining)
*
* @since 2.1
*/
public JsonNode set(String fieldName, JsonNode value)
{
if (value == null) {
value = nullNode();
}
_put(fieldName, value);
return this;
}
/**
* Method for adding given properties to this object node, overriding
* any existing values for those properties.
*
* @param properties Properties to add
*
* @return This node after adding/replacing property values (to allow chaining)
*
* @since 2.1
*/
public JsonNode setAll(Map<String,JsonNode> properties)
{
if (_children == null) {
_children = _createMap();
} else {
for (Map.Entry<String, JsonNode> en : properties.entrySet()) {
JsonNode n = en.getValue();
if (n == null) {
n = nullNode();
}
_children.put(en.getKey(), n);
}
}
return this;
}
/**
* Method for adding all properties of the given Object, overriding
* any existing values for those properties.
*
* @param other Object of which properties to add to this object
*
* @return This node after addition (to allow chaining)
*
* @since 2.1
*/
public JsonNode setAll(ObjectNode other)
{
int len = other.size();
if (len > 0) {
if (_children == null) {
_children = _createMap(len);
}
other.putContentsTo(_children);
}
return this;
}
/**
* Method for replacing value of specific property with passed
* value, and returning value (or null if none).
*
* @param fieldName Property of which value to replace
* @param value Value to set property to, replacing old value if any
*
* @return Old value of the property; null if there was no such property
* with value
*
* @since 2.1
*/
public JsonNode replace(String fieldName, JsonNode value)
{
if (value == null) { // let's not store 'raw' nulls but nodes
value = nullNode();
}
return _put(fieldName, value);
}
/**
* Method for removing field entry from this ObjectNode, and
* returning instance after removal.
*
* @return This node after removing entry (if any)
*
* @since 2.1
*/
public JsonNode without(String fieldName)
{
if (_children != null) {
_children.remove(fieldName);
}
return this;
}
/**
* Method for removing specified field properties out of
* this ObjectNode.
*
* @param fieldNames Names of fields to remove
*
* @return This node after removing entries
*
* @since 2.1
*/
public ObjectNode without(Collection<String> fieldNames)
{
if (_children != null) {
for (String fieldName : fieldNames) {
_children.remove(fieldName);
}
}
return this;
}
/*
/**********************************************************
/* Extended ObjectNode API, mutators, generic
/**********************************************************
*/
/**
* Method that will set specified field, replacing old value, if any.
*
* @param value to set field to; if null, will be converted
* to a {@link NullNode} first (to remove field entry, call
* {@link #remove} instead)
*<p>
* NOTE: this method will be <b>deprecated</b> in 2.2; and should
* be replace with either
* {@link #set(String,JsonNode)} or {@link #replace(String,JsonNode)},
* depending on which return value is desired for possible chaining.
*
* @return Old value of the field, if any; null if there was no
* old value.
*/
public JsonNode put(String fieldName, JsonNode value)
{
if (value == null) { // let's not store 'raw' nulls but nodes
value = nullNode();
}
return _put(fieldName, value);
}
/**
* Method for removing field entry from this ObjectNode.
* Will return value of the field, if such field existed;
* null if not.
*
* @return Value of specified field, if it existed; null if not
*/
public JsonNode remove(String fieldName)
{
if (_children != null) {
return _children.remove(fieldName);
}
return null;
}
/**
* Method for removing specified field properties out of
* this ObjectNode.
*
* @param fieldNames Names of fields to remove
*
* @return This node after removing entries
*/
public ObjectNode remove(Collection<String> fieldNames)
{
if (_children != null) {
for (String fieldName : fieldNames) {
_children.remove(fieldName);
}
}
return this;
}
/**
* Method for removing all field properties, such that this
* ObjectNode will contain no properties after call.
*
* @return This node after removing all entries
*/
@Override
public ObjectNode removeAll()
{
_children = null;
return this;
}
/**
* Method for adding given properties to this object node, overriding
* any existing values for those properties.
*<p>
* NOTE: this method will be <b>deprecated</b> in 2.2; and should
* be replace with {@link #setAll(Map)}.
*
* @param properties Properties to add
*
* @return This node after adding/replacing property values (to allow chaining)
*/
public JsonNode putAll(Map<String,JsonNode> properties) {
return setAll(properties);
}
/**
* Method for adding all properties of the given Object, overriding
* any existing values for those properties.
*<p>
* NOTE: this method will be <b>deprecated</b> in 2.2; and should
* be replace with {@link #setAll(ObjectNode)}.
*
* @param other Object of which properties to add to this object
*
* @return This node (to allow chaining)
*/
public JsonNode putAll(ObjectNode other) {
return setAll(other);
}
/**
* Method for removing all field properties out of this ObjectNode
* <b>except</b> for ones specified in argument.
*
* @param fieldNames Fields to <b>retain</b> in this ObjectNode
*
* @return This node (to allow call chaining)
*/
public ObjectNode retain(Collection<String> fieldNames)
{
if (_children != null) {
Iterator<Map.Entry<String,JsonNode>> entries = _children.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<String, JsonNode> entry = entries.next();
if (!fieldNames.contains(entry.getKey())) {
entries.remove();
}
}
}
return this;
}
/**
* Method for removing all field properties out of this ObjectNode
* <b>except</b> for ones specified in argument.
*
* @param fieldNames Fields to <b>retain</b> in this ObjectNode
*
* @return This node (to allow call chaining)
*/
public ObjectNode retain(String... fieldNames) {
return retain(Arrays.asList(fieldNames));
}
/*
/**********************************************************
/* Extended ObjectNode API, mutators, typed
/**********************************************************
*/
/**
* Method that will construct an ArrayNode and add it as a
* field of this ObjectNode, replacing old value, if any.
*<p>
* <b>NOTE</b>: Unlike all <b>put(...)</b> methods, return value
* is <b>NOT</b> this <code>ObjectNode</code>, but the
* <b>newly created</b> <code>ArrayNode</code> instance.
*
* @return Newly constructed ArrayNode (NOT the old value,
* which could be of any type)
*/
public ArrayNode putArray(String fieldName)
{
ArrayNode n = arrayNode();
_put(fieldName, n);
return n;
}
/**
* Method that will construct an ObjectNode and add it as a
* field of this ObjectNode, replacing old value, if any.
*<p>
* <b>NOTE</b>: Unlike all <b>put(...)</b> methods, return value
* is <b>NOT</b> this <code>ObjectNode</code>, but the
* <b>newly created</b> <code>ObjectNode</code> instance.
*
* @return Newly constructed ObjectNode (NOT the old value,
* which could be of any type)
*/
public ObjectNode putObject(String fieldName)
{
ObjectNode n = objectNode();
_put(fieldName, n);
return n;
}
/**
* @return This node (to allow chaining)
*/
public ObjectNode putPOJO(String fieldName, Object pojo) {
_put(fieldName, POJONode(pojo));
return this;
}
/**
* @return This node (to allow chaining)
*/
public ObjectNode putNull(String fieldName)
{
_put(fieldName, nullNode());
return this;
}
/**
* Method for setting value of a field to specified numeric value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, int v) {
_put(fieldName, numberNode(v));
return this;
}
/**
* Alternative method that we need to avoid bumping into NPE issues
* with auto-unboxing.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, Integer value) {
if (value == null) {
_put(fieldName, nullNode());
} else {
_put(fieldName, numberNode(value.intValue()));
}
return this;
}
/**
* Method for setting value of a field to specified numeric value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, long v) {
_put(fieldName, numberNode(v));
return this;
}
/**
* Alternative method that we need to avoid bumping into NPE issues
* with auto-unboxing.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, Long value) {
if (value == null) {
_put(fieldName, nullNode());
} else {
_put(fieldName, numberNode(value.longValue()));
}
return this;
}
/**
* Method for setting value of a field to specified numeric value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, float v) {
_put(fieldName, numberNode(v));
return this;
}
/**
* Alternative method that we need to avoid bumping into NPE issues
* with auto-unboxing.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, Float value) {
if (value == null) {
_put(fieldName, nullNode());
} else {
_put(fieldName, numberNode(value.floatValue()));
}
return this;
}
/**
* Method for setting value of a field to specified numeric value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, double v) {
_put(fieldName, numberNode(v));
return this;
}
/**
* Alternative method that we need to avoid bumping into NPE issues
* with auto-unboxing.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, Double value) {
if (value == null) {
_put(fieldName, nullNode());
} else {
_put(fieldName, numberNode(value.doubleValue()));
}
return this;
}
/**
* Method for setting value of a field to specified numeric value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, BigDecimal v) {
if (v == null) {
putNull(fieldName);
} else {
_put(fieldName, numberNode(v));
}
return this;
}
/**
* Method for setting value of a field to specified String value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, String v) {
if (v == null) {
putNull(fieldName);
} else {
_put(fieldName, textNode(v));
}
return this;
}
/**
* Method for setting value of a field to specified String value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, boolean v) {
_put(fieldName, booleanNode(v));
return this;
}
/**
* Alternative method that we need to avoid bumping into NPE issues
* with auto-unboxing.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, Boolean value) {
if (value == null) {
_put(fieldName, nullNode());
} else {
_put(fieldName, booleanNode(value.booleanValue()));
}
return this;
}
/**
* Method for setting value of a field to specified binary value
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, byte[] v) {
if (v == null) {
_put(fieldName, nullNode());
} else {
_put(fieldName, binaryNode(v));
}
return this;
}
/*
/**********************************************************
/* Overridable methods
/**********************************************************
*/
/**
* Internal factory method for creating {@link Map} used for storing
* child nodes.
* Overridable by sub-classes, used when caller does not know what
* optimal size would, used for example when constructing a Map when adding
* the first one.
*
* @since 2.1
*/
protected Map<String, JsonNode> _createMap() {
return new LinkedHashMap<String, JsonNode>();
}
/**
* Internal factory method for creating {@link Map} used for storing
* child nodes.
* Overridable by sub-classes, used when caller has an idea of what
* optimal size should be: used when copying contents of an existing node.
*
* @since 2.1
*/
protected Map<String, JsonNode> _createMap(int defaultSize) {
return new LinkedHashMap<String, JsonNode>(defaultSize);
}
/*
/**********************************************************
/* Package methods (for other node classes to use)
/**********************************************************
*/
protected void putContentsTo(Map<String,JsonNode> dst)
{
if (_children != null) {
for (Map.Entry<String,JsonNode> en : _children.entrySet()) {
dst.put(en.getKey(), en.getValue());
}
}
}
/*
/**********************************************************
/* Standard methods
/**********************************************************
*/
@Override
public boolean equals(Object o)
{
if (o == this) return true;
if (o == null) return false;
if (o.getClass() != getClass()) {
return false;
}
ObjectNode other = (ObjectNode) o;
if (other.size() != size()) {
return false;
}
if (_children != null) {
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
String key = en.getKey();
JsonNode value = en.getValue();
JsonNode otherValue = other.get(key);
if (otherValue == null || !otherValue.equals(value)) {
return false;
}
}
}
return true;
}
@Override
public int hashCode()
{
return (_children == null) ? -1 : _children.hashCode();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(32 + (size() << 4));
sb.append("{");
if (_children != null) {
int count = 0;
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
if (count > 0) {
sb.append(",");
}
++count;
TextNode.appendQuoted(sb, en.getKey());
sb.append(':');
sb.append(en.getValue().toString());
}
}
sb.append("}");
return sb.toString();
}
/*
/**********************************************************
/* Internal methods
/**********************************************************
*/
private final JsonNode _put(String fieldName, JsonNode value)
{
if (_children == null) {
_children = _createMap();
}
return _children.put(fieldName, value);
}
/*
/**********************************************************
/* Helper classes
/**********************************************************
*/
/**
* For efficiency, let's share the "no fields" iterator...
*/
protected static class NoFieldsIterator
implements Iterator<Map.Entry<String, JsonNode>>
{
final static NoFieldsIterator instance = new NoFieldsIterator();
private NoFieldsIterator() { }
// @Override
public boolean hasNext() { return false; }
// @Override
public Map.Entry<String,JsonNode> next() { throw new NoSuchElementException(); }
// @Override
public void remove() { // or IllegalOperationException?
throw new IllegalStateException();
}
}
}