io.honeybadger.com.fasterxml.jackson.databind.node.ObjectNode Maven / Gradle / Ivy
package com.fasterxml.jackson.databind.node;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
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;
/**
* Node that maps to JSON Object structures in JSON content.
*
* Note: class was final
temporarily for Jackson 2.2.
*/
public class ObjectNode
extends ContainerNode
implements java.io.Serializable
{
private static final long serialVersionUID = 1L; // since 2.10
// Note: LinkedHashMap for backwards compatibility
protected final Map _children;
public ObjectNode(JsonNodeFactory nc) {
super(nc);
_children = new LinkedHashMap();
}
/**
* @since 2.4
*/
public ObjectNode(JsonNodeFactory nc, Map 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 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 // since 2.10
public boolean isEmpty() { return _children.isEmpty(); }
@Override
public Iterator elements() {
return _children.values().iterator();
}
@Override
public JsonNode get(int index) { return null; }
@Override
public JsonNode get(String propertyName) {
return _children.get(propertyName);
}
@Override
public Iterator fieldNames() {
return _children.keySet().iterator();
}
@Override
public JsonNode path(int index) {
return MissingNode.getInstance();
}
@Override
public JsonNode path(String propertyName)
{
JsonNode n = _children.get(propertyName);
if (n != null) {
return n;
}
return MissingNode.getInstance();
}
@Override
public JsonNode required(String propertyName) {
JsonNode n = _children.get(propertyName);
if (n != null) {
return n;
}
return _reportRequiredViolation("No value for property '%s' of `ObjectNode`", propertyName);
}
/**
* Method to use for accessing all properties (with both names
* and values) of this JSON Object.
*/
@Override
public Iterator> fields() {
return _children.entrySet().iterator();
}
@SuppressWarnings("unchecked")
@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;
}
@SuppressWarnings("unchecked")
@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 comparator, JsonNode o)
{
if (!(o instanceof ObjectNode)) {
return false;
}
ObjectNode other = (ObjectNode) o;
Map m1 = _children;
Map m2 = other._children;
final int len = m1.size();
if (m2.size() != len) {
return false;
}
for (Map.Entry 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 propertyName)
{
for (Map.Entry entry : _children.entrySet()) {
if (propertyName.equals(entry.getKey())) {
return entry.getValue();
}
JsonNode value = entry.getValue().findValue(propertyName);
if (value != null) {
return value;
}
}
return null;
}
@Override
public List findValues(String propertyName, List foundSoFar)
{
for (Map.Entry entry : _children.entrySet()) {
if (propertyName.equals(entry.getKey())) {
if (foundSoFar == null) {
foundSoFar = new ArrayList();
}
foundSoFar.add(entry.getValue());
} else { // only add children if parent not added
foundSoFar = entry.getValue().findValues(propertyName, foundSoFar);
}
}
return foundSoFar;
}
@Override
public List findValuesAsText(String propertyName, List foundSoFar)
{
for (Map.Entry entry : _children.entrySet()) {
if (propertyName.equals(entry.getKey())) {
if (foundSoFar == null) {
foundSoFar = new ArrayList();
}
foundSoFar.add(entry.getValue().asText());
} else { // only add children if parent not added
foundSoFar = entry.getValue().findValuesAsText(propertyName,
foundSoFar);
}
}
return foundSoFar;
}
@Override
public ObjectNode findParent(String propertyName)
{
for (Map.Entry entry : _children.entrySet()) {
if (propertyName.equals(entry.getKey())) {
return this;
}
JsonNode value = entry.getValue().findParent(propertyName);
if (value != null) {
return (ObjectNode) value;
}
}
return null;
}
@Override
public List findParents(String propertyName, List foundSoFar)
{
for (Map.Entry entry : _children.entrySet()) {
if (propertyName.equals(entry.getKey())) {
if (foundSoFar == null) {
foundSoFar = new ArrayList();
}
foundSoFar.add(this);
} else { // only add children if parent not added
foundSoFar = entry.getValue()
.findParents(propertyName, 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 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 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 property, replacing old value, if any.
* Note that this is identical to {@link #replace(String, JsonNode)},
* except for return value.
*
* NOTE: added to replace those uses of {@link #put(String, JsonNode)}
* where chaining with 'this' is desired.
*
* NOTE: co-variant return type since 2.10
*
* @param propertyName Name of property to set
* @param value Value to set property to; if null, will be converted
* to a {@link NullNode} first (to remove a property, call
* {@link #remove} instead)
*
* @return This node after adding/replacing property value (to allow chaining)
*
* @since 2.1
*/
@SuppressWarnings("unchecked")
public T set(String propertyName, JsonNode value)
{
if (value == null) {
value = nullNode();
}
_children.put(propertyName, value);
return (T) this;
}
/**
* Method for adding given properties to this object node, overriding
* any existing values for those properties.
*
* NOTE: co-variant return type since 2.10
*
* @param properties Properties to add
*
* @return This node after adding/replacing property values (to allow chaining)
*
* @since 2.1
*/
@SuppressWarnings("unchecked")
public T setAll(Map properties)
{
for (Map.Entry en : properties.entrySet()) {
JsonNode n = en.getValue();
if (n == null) {
n = nullNode();
}
_children.put(en.getKey(), n);
}
return (T) this;
}
/**
* Method for adding all properties of the given Object, overriding
* any existing values for those properties.
*
* NOTE: co-variant return type since 2.10
*
* @param other Object of which properties to add to this object
*
* @return This node after addition (to allow chaining)
*
* @since 2.1
*/
@SuppressWarnings("unchecked")
public T setAll(ObjectNode other)
{
_children.putAll(other._children);
return (T) this;
}
/**
* Method for replacing value of specific property with passed
* value, and returning value (or null if none).
*
* @param propertyName 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 propertyName, JsonNode value)
{
if (value == null) { // let's not store 'raw' nulls but nodes
value = nullNode();
}
return _children.put(propertyName, value);
}
/**
* Method for removing property from this ObjectNode, and
* returning instance after removal.
*
* NOTE: co-variant return type since 2.10
*
* @return This node after removing property (if any)
*
* @since 2.1
*/
@SuppressWarnings("unchecked")
public T without(String propertyName)
{
_children.remove(propertyName);
return (T) this;
}
/**
* Method for removing specified field properties out of
* this ObjectNode.
*
* NOTE: co-variant return type since 2.10
*
* @param propertyNames Names of properties to remove
*
* @return This node after removing entries
*
* @since 2.1
*/
@SuppressWarnings("unchecked")
public T without(Collection propertyNames)
{
_children.keySet().removeAll(propertyNames);
return (T) this;
}
/*
/**********************************************************
/* Extended ObjectNode API, mutators, generic
/**********************************************************
*/
/**
* Method that will set specified property, replacing old value, if any.
*
* @param propertyName Name of property to set
* @param value Value to set to property; if null, will be converted
* to a {@link NullNode} first (to remove a property, call
* {@link #remove} instead).
*
* @return Old value of the property, if any; {@code 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 propertyName, JsonNode value)
{
if (value == null) { // let's not store 'raw' nulls but nodes
value = nullNode();
}
return _children.put(propertyName, value);
}
/**
* Method that will set value of specified property if (and only if)
* it had no set value previously.
* Note that explicitly set {@code null} is a value.
* Functionally equivalent to:
*
* if (get(propertyName) == null) {
* set(propertyName, value);
* return null;
* } else {
* return get(propertyName);
* }
*
*
* @param propertyName Name of property to set
* @param value Value to set to property (if and only if it had no value previously);
* if null, will be converted to a {@link NullNode} first.
*
* @return Old value of the property, if any (in which case value was not changed);
* null if there was no old value (in which case value is now set)
*
* @since 2.13
*/
public JsonNode putIfAbsent(String propertyName, JsonNode value)
{
if (value == null) { // let's not store 'raw' nulls but nodes
value = nullNode();
}
return _children.putIfAbsent(propertyName, value);
}
/**
* Method for removing a property from this {@code ObjectNode}.
* Will return previous value of the property, if such property existed;
* null if not.
*
* @return Value of specified property, if it existed; null if not
*/
public JsonNode remove(String propertyName) {
return _children.remove(propertyName);
}
/**
* Method for removing specified field properties out of
* this ObjectNode.
*
* @param propertyNames Names of fields to remove
*
* @return This node after removing entries
*/
public ObjectNode remove(Collection propertyNames)
{
_children.keySet().removeAll(propertyNames);
return this;
}
/**
* Method for removing all 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 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 properties out of this ObjectNode
* except for ones specified in argument.
*
* @param propertyNames Fields to retain in this ObjectNode
*
* @return This node (to allow call chaining)
*/
public ObjectNode retain(Collection propertyNames)
{
_children.keySet().retainAll(propertyNames);
return this;
}
/**
* Method for removing all properties out of this ObjectNode
* except for ones specified in argument.
*
* @param propertyNames Fields to retain in this ObjectNode
*
* @return This node (to allow call chaining)
*/
public ObjectNode retain(String... propertyNames) {
return retain(Arrays.asList(propertyNames));
}
/*
/**********************************************************
/* Extended ObjectNode API, mutators, typed
/**********************************************************
*/
/**
* Method that will construct an ArrayNode and add it as a
* property of this {@code ObjectNode}, replacing old value, if any.
*
* NOTE: Unlike all put(...) methods, return value
* is NOT this ObjectNode
, but the
* newly created ArrayNode
instance.
*
* @return Newly constructed ArrayNode (NOT the old value,
* which could be of any type)
*/
public ArrayNode putArray(String propertyName)
{
ArrayNode n = arrayNode();
_put(propertyName, n);
return n;
}
/**
* Method that will construct an ObjectNode and add it as a
* property of this {@code ObjectNode}, replacing old value, if any.
*
* NOTE: Unlike all put(...) methods, return value
* is NOT this ObjectNode
, but the
* newly created ObjectNode
instance.
*
* @return Newly constructed ObjectNode (NOT the old value,
* which could be of any type)
*/
public ObjectNode putObject(String propertyName)
{
ObjectNode n = objectNode();
_put(propertyName, n);
return n;
}
/**
* Method for adding an opaque Java value as the value of specified property.
* Value can be serialized like any other property, as long as Jackson can
* serialize it. Despite term "POJO" this allows use of about any Java type, including
* {@link java.util.Map}s, {@link java.util.Collection}s, as well as Beans (POJOs),
* primitives/wrappers and even {@link JsonNode}s.
* Method is most commonly useful when composing content to serialize from heterogenous
* sources.
*
* NOTE: if using {@link JsonNode#toString()} (or {@link JsonNode#toPrettyString()}
* support for serialization may be more limited, compared to serializing node
* with specifically configured {@link ObjectMapper}.
*
* @param propertyName Name of property to set.
* @param pojo Java value to set as the property value
*
* @return This {@code ObjectNode} (to allow chaining)
*/
public ObjectNode putPOJO(String propertyName, Object pojo) {
return _put(propertyName, pojoNode(pojo));
}
/**
* @since 2.6
*/
public ObjectNode putRawValue(String propertyName, RawValue raw) {
return _put(propertyName, rawValueNode(raw));
}
/**
* Method for setting value of a property to explicit {@code null} value.
*
* @param propertyName Name of property to set.
*
* @return This {@code ObjectNode} (to allow chaining)
*/
public ObjectNode putNull(String propertyName)
{
_children.put(propertyName, nullNode());
return this;
}
/**
* Method for setting value of a property to specified numeric value.
*
* @return This node (to allow chaining)
*/
public ObjectNode put(String propertyName, short v) {
return _put(propertyName, 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.
*
* 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();
}
/*
/**********************************************************
/* Internal methods (overridable)
/**********************************************************
*/
protected ObjectNode _put(String fieldName, JsonNode value)
{
_children.put(fieldName, value);
return this;
}
}