blob: c80d3addee84110296be07da6acd9a19fdbc4718 [file] [log] [blame]
package com.fasterxml.jackson.databind.node;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.util.RawValue;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
/**
* Node that maps to JSON Object structures in JSON content.
*<p>
* Note: class was <code>final</code> temporarily for Jackson 2.2.
*/
public class ObjectNode
extends ContainerNode<ObjectNode>
{
// Note: LinkedHashMap for backwards compatibility
protected final Map<String, JsonNode> _children;
public ObjectNode(JsonNodeFactory nc) {
super(nc);
_children = new LinkedHashMap<String, JsonNode>();
}
/**
* @since 2.4
*/
public ObjectNode(JsonNodeFactory nc, Map<String, JsonNode> kids) {
super(nc);
_children = kids;
}
@Override
protected JsonNode _at(JsonPointer ptr) {
return get(ptr.getMatchingProperty());
}
/* 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()
{
ObjectNode ret = new ObjectNode(_nodeFactory);
for (Map.Entry<String, JsonNode> entry: _children.entrySet())
ret._children.put(entry.getKey(), entry.getValue().deepCopy());
return ret;
}
/*
/**********************************************************
/* Overrides for JsonSerializable.Base
/**********************************************************
*/
@Override
public boolean isEmpty(SerializerProvider serializers) {
return _children.isEmpty();
}
/*
/**********************************************************
/* Implementation of core JsonNode API
/**********************************************************
*/
@Override
public JsonNodeType getNodeType() {
return JsonNodeType.OBJECT;
}
@Override
public final boolean isObject() {
return true;
}
@Override public JsonToken asToken() { return JsonToken.START_OBJECT; }
@Override
public int size() {
return _children.size();
}
@Override
public Iterator<JsonNode> elements() {
return _children.values().iterator();
}
@Override
public JsonNode get(int index) { return null; }
@Override
public JsonNode get(String fieldName) {
return _children.get(fieldName);
}
@Override
public Iterator<String> fieldNames() {
return _children.keySet().iterator();
}
@Override
public JsonNode path(int index) {
return MissingNode.getInstance();
}
@Override
public JsonNode path(String fieldName)
{
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() {
return _children.entrySet().iterator();
}
@Override
public ObjectNode with(String propertyName) {
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)
{
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;
}
@Override
public boolean equals(Comparator<JsonNode> comparator, JsonNode o)
{
if (!(o instanceof ObjectNode)) {
return false;
}
ObjectNode other = (ObjectNode) o;
Map<String, JsonNode> m1 = _children;
Map<String, JsonNode> m2 = other._children;
final int len = m1.size();
if (m2.size() != len) {
return false;
}
for (Map.Entry<String, JsonNode> entry : m1.entrySet()) {
JsonNode v2 = m2.get(entry.getKey());
if ((v2 == null) || !entry.getValue().equals(comparator, v2)) {
return false;
}
}
return true;
}
/*
/**********************************************************
/* Public API, finding value nodes
/**********************************************************
*/
@Override
public JsonNode findValue(String fieldName)
{
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)
{
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)
{
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)
{
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)
{
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 void serialize(JsonGenerator g, SerializerProvider provider)
throws IOException
{
@SuppressWarnings("deprecation")
boolean trimEmptyArray = (provider != null) &&
!provider.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
g.writeStartObject(this);
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
/* 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 value = (BaseJsonNode) en.getValue();
// as per [databind#867], see if WRITE_EMPTY_JSON_ARRAYS feature is disabled,
// if the feature is disabled, then should not write an empty array
// to the output, so continue to the next element in the iteration
if (trimEmptyArray && value.isArray() && value.isEmpty(provider)) {
continue;
}
g.writeFieldName(en.getKey());
value.serialize(g, provider);
}
g.writeEndObject();
}
@Override
public void serializeWithType(JsonGenerator g, SerializerProvider provider,
TypeSerializer typeSer)
throws IOException
{
@SuppressWarnings("deprecation")
boolean trimEmptyArray = (provider != null) &&
!provider.isEnabled(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
typeSer.typeId(this, JsonToken.START_OBJECT));
for (Map.Entry<String, JsonNode> en : _children.entrySet()) {
BaseJsonNode value = (BaseJsonNode) en.getValue();
// check if WRITE_EMPTY_JSON_ARRAYS feature is disabled,
// if the feature is disabled, then should not write an empty array
// to the output, so continue to the next element in the iteration
if (trimEmptyArray && value.isArray() && value.isEmpty(provider)) {
continue;
}
g.writeFieldName(en.getKey());
value.serialize(g, provider);
}
typeSer.writeTypeSuffix(g, typeIdDef);
}
/*
/**********************************************************
/* 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();
}
_children.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,? extends JsonNode> properties)
{
for (Map.Entry<String,? extends 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)
{
_children.putAll(other._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 _children.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)
{
_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)
{
_children.keySet().removeAll(fieldNames);
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)
*
* @return Old value of the field, if any; null if there was no
* old value.
*
* @deprecated Since 2.4 use either {@link #set(String,JsonNode)} or {@link #replace(String,JsonNode)},
*/
@Deprecated
public JsonNode put(String fieldName, JsonNode value)
{
if (value == null) { // let's not store 'raw' nulls but nodes
value = nullNode();
}
return _children.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) {
return _children.remove(fieldName);
}
/**
* 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)
{
_children.keySet().removeAll(fieldNames);
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.clear();
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)
*
* @deprecated Since 2.4 use {@link #setAll(Map)},
*/
@Deprecated
public JsonNode putAll(Map<String,? extends JsonNode> properties) {
return setAll(properties);
}
/**
* 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 (to allow chaining)
*
* @deprecated Since 2.4 use {@link #setAll(ObjectNode)},
*/
@Deprecated
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)
{
_children.keySet().retainAll(fieldNames);
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) {
return _put(fieldName, pojoNode(pojo));
}
/**
* @since 2.6
*/
public ObjectNode putRawValue(String fieldName, RawValue raw) {
return _put(fieldName, rawValueNode(raw));
}
/**
* @return This node (to allow chaining)
*/
public ObjectNode putNull(String fieldName)
{
_children.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, short v) {
return _put(fieldName, numberNode(v));
}
/**
* 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, Short v) {
return _put(fieldName, (v == null) ? nullNode()
: numberNode(v.shortValue()));
}
/**
* Method for setting value of a field to specified numeric value.
* The underlying {@link JsonNode} that will be added is constructed
* using {@link JsonNodeFactory#numberNode(int)}, and may be
* "smaller" (like {@link ShortNode}) in cases where value fits within
* range of a smaller integral numeric value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, int v) {
return _put(fieldName, numberNode(v));
}
/**
* 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 v) {
return _put(fieldName, (v == null) ? nullNode()
: numberNode(v.intValue()));
}
/**
* Method for setting value of a field to specified numeric value.
* The underlying {@link JsonNode} that will be added is constructed
* using {@link JsonNodeFactory#numberNode(long)}, and may be
* "smaller" (like {@link IntNode}) in cases where value fits within
* range of a smaller integral numeric value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, long v) {
return _put(fieldName, numberNode(v));
}
/**
* Method for setting value of a field to specified numeric value.
* The underlying {@link JsonNode} that will be added is constructed
* using {@link JsonNodeFactory#numberNode(Long)}, and may be
* "smaller" (like {@link IntNode}) in cases where value fits within
* range of a smaller integral numeric value.
* <p>
* Note that this is alternative to {@link #put(String, long)} needed to avoid
* bumping into NPE issues with auto-unboxing.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, Long v) {
return _put(fieldName, (v == null) ? nullNode()
: numberNode(v.longValue()));
}
/**
* Method for setting value of a field to specified numeric value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, float v) {
return _put(fieldName, numberNode(v));
}
/**
* 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 v) {
return _put(fieldName, (v == null) ? nullNode()
: numberNode(v.floatValue()));
}
/**
* Method for setting value of a field to specified numeric value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, double v) {
return _put(fieldName, numberNode(v));
}
/**
* 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 v) {
return _put(fieldName, (v == null) ? nullNode()
: numberNode(v.doubleValue()));
}
/**
* Method for setting value of a field to specified numeric value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, BigDecimal v) {
return _put(fieldName, (v == null) ? nullNode()
: numberNode(v));
}
/**
* Method for setting value of a field to specified numeric value.
*
* @return This node (to allow chaining)
*
* @since 2.9
*/
public ObjectNode put(String fieldName, BigInteger v) {
return _put(fieldName, (v == null) ? nullNode()
: numberNode(v));
}
/**
* Method for setting value of a field to specified String value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, String v) {
return _put(fieldName, (v == null) ? nullNode()
: textNode(v));
}
/**
* Method for setting value of a field to specified String value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, boolean v) {
return _put(fieldName, booleanNode(v));
}
/**
* 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 v) {
return _put(fieldName, (v == null) ? nullNode()
: booleanNode(v.booleanValue()));
}
/**
* Method for setting value of a field to specified binary value
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String fieldName, byte[] v) {
return _put(fieldName, (v == null) ? nullNode()
: binaryNode(v));
}
/*
/**********************************************************
/* Standard methods
/**********************************************************
*/
@Override
public boolean equals(Object o)
{
if (o == this) return true;
if (o == null) return false;
if (o instanceof ObjectNode) {
return _childrenEqual((ObjectNode) o);
}
return false;
}
/**
* @since 2.3
*/
protected boolean _childrenEqual(ObjectNode other)
{
return _children.equals(other._children);
}
@Override
public int hashCode()
{
return _children.hashCode();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(32 + (size() << 4));
sb.append("{");
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 (overridable)
/**********************************************************
*/
protected ObjectNode _put(String fieldName, JsonNode value)
{
_children.put(fieldName, value);
return this;
}
}