All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fasterxml.jackson.databind.node.ObjectNode Maven / Gradle / Ivy

There is a newer version: 2.17.0
Show newest version
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
{
    // note: until 2.1, was explicitly `LinkedHashMap`
    protected Map _children = null;

    public ObjectNode(JsonNodeFactory nc) { super(nc); }

    protected ObjectNode(JsonNodeFactory nc, Map 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 newKids = _createMap(Math.max(4, len));
        for (Map.Entry 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 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 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> 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 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 findValues(String fieldName, List foundSoFar)
    {
        if (_children != null) {
            for (Map.Entry entry : _children.entrySet()) {
                if (fieldName.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(fieldName, foundSoFar);
                }
            }
        }
        return foundSoFar;
    }

    @Override
    public List findValuesAsText(String fieldName, List foundSoFar)
    {
        if (_children != null) {
            for (Map.Entry entry : _children.entrySet()) {
                if (fieldName.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(fieldName, foundSoFar);
                }
            }
        }
        return foundSoFar;
    }
    
    @Override
    public ObjectNode findParent(String fieldName)
    {
        if (_children != null) {
            for (Map.Entry 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 findParents(String fieldName, List foundSoFar)
    {
        if (_children != null) {
            for (Map.Entry entry : _children.entrySet()) {
                if (fieldName.equals(entry.getKey())) {
                    if (foundSoFar == null) {
                        foundSoFar = new ArrayList();
                    }
                    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 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 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.
     *

* 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 properties) { if (_children == null) { _children = _createMap(); } for (Map.Entry 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 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) *

* NOTE: this method will be deprecated 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 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. *

* NOTE: this method will be deprecated 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 properties) { return setAll(properties); } /** * Method for adding all properties of the given Object, overriding * any existing values for those properties. *

* NOTE: this method will be deprecated 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 * except for ones specified in argument. * * @param fieldNames Fields to retain in this ObjectNode * * @return This node (to allow call chaining) */ public ObjectNode retain(Collection fieldNames) { if (_children != null) { Iterator> entries = _children.entrySet().iterator(); while (entries.hasNext()) { Map.Entry entry = entries.next(); if (!fieldNames.contains(entry.getKey())) { entries.remove(); } } } return this; } /** * Method for removing all field properties out of this ObjectNode * except for ones specified in argument. * * @param fieldNames Fields to retain 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. *

* 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 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. *

* 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 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 _createMap() { return new LinkedHashMap(); } /** * 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 _createMap(int defaultSize) { return new LinkedHashMap(defaultSize); } /* /********************************************************** /* Package methods (for other node classes to use) /********************************************************** */ protected void putContentsTo(Map dst) { if (_children != null) { for (Map.Entry en : _children.entrySet()) { dst.put(en.getKey(), en.getValue()); } } } /* /********************************************************** /* Standard methods /********************************************************** */ @Override public final boolean equals(Object o) { if (o == this) return true; if (o == null) return false; // minor improvement, wrt [Issue#70] if (o.getClass() != getClass() && !(o instanceof ObjectNode)) { return false; } /* This is bit convoluted, but the goal is to make it possible to * fully override equality comparison, even though it is * asymmetric (i.e. can be called on either side, but we * want behavior to match). */ return _equals((ObjectNode) o); } /** * Method that sub-classes should override, if equality comparison * needs additional verification beyond defaults. * * @since 2.1 */ protected boolean _equals(ObjectNode other) { return _stdEquals(other) &&_customEquals(other) && other._customEquals(this) ; } /** * Method that sub-classes should override, if equality comparison * needs additional verification beyond defaults. * * @since 2.1 */ protected boolean _customEquals(ObjectNode other) { return true; } /** * Standard equality checks, which may also be overridden by * sub-classes if necessary (but usually isn't). * * @since 2.1 */ protected final boolean _stdEquals(ObjectNode other) { if (other.size() != size()) { return false; } if (_children != null) { for (Map.Entry 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 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> { final static NoFieldsIterator instance = new NoFieldsIterator(); private NoFieldsIterator() { } // @Override public boolean hasNext() { return false; } // @Override public Map.Entry next() { throw new NoSuchElementException(); } // @Override public void remove() { // or IllegalOperationException? throw new IllegalStateException(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy