Class JSNode

java.lang.Object
io.inversion.utils.JSNode
All Implemented Interfaces:
Map<String,Object>
Direct Known Subclasses:
JSArray

public class JSNode extends Object implements Map<String,Object>
Yet another JavaScript/JSON map object representation with a few superpowers.

Inversion encourages working with JSON data structures abstractly instead of transcoding them into a concrete Java object model. So JSNode and JSArray were designed to make it as easy as possible to work with JSON in its "native form."

JSNode and JSArray are a one stop shop for:

  • Parsing and printing JSON
  • Finding elements of a document with JSONPath and JSONPointer
  • Diff and patching with JSONPatch

Property name case is preserved but considered case insensitive when accessing a property by name.

Property iteration or is preserved based on insertion order.

It is possible to create a document with a child JSNode appearing multiple times in the document including circular reference loops. When printing a document, if a JSNode has previously been printed AND it has an 'href' property, instead of erroring, the printer will write an '@link' property pointing to the previously printed href. If the JSNode does not have an 'href' an error will be thrown.

Under the covers this Jackson is used as the json parser.

See Also:
  • Constructor Details

    • JSNode

      public JSNode()
      Creates an empty JSNode.
    • JSNode

      public JSNode(Object... nameValuePairs)
      Creates a JSNode with nameValuePairs as the initial properties.

      The first and every other element in nameValuePairs should be a string.

      Parameters:
      nameValuePairs - the name value pairs to add
      See Also:
    • JSNode

      public JSNode(Map nameValuePairs)
      Creates a JSNode with nameValuePairs as the initial properties.
      Parameters:
      nameValuePairs - the name value pairs to add
      See Also:
  • Method Details

    • parseJson

      public static Object parseJson(String json)
      Turns a JSON string in to JSNode (maps), JSArray (lists), String numbers and booleans.

      Jackson is the underlying parser

      Parameters:
      json - the json string to parse
      Returns:
      a String, number, boolean, JSNode or JSArray
    • parseJsonNode

      public static JSNode parseJsonNode(String json) throws ClassCastException
      Utility overloading of parseJson(String) to cast the return as a JSNode
      Parameters:
      json - the json string to parse
      Returns:
      the result of parsing the json document cast to a JSNode
      Throws:
      ClassCastException - if the result of parsing is not a JSNode
    • parseJsonArray

      public static JSArray parseJsonArray(String json)
      Utility overloading of parseJson(String) to cast the return as a JSArray
      Parameters:
      json - a json string containing an as the root element
      Returns:
      the result of parsing the json document cast to a JSArray
      Throws:
      ClassCastException - if the result of parsing is not a JSArray
    • findAll

      public JSArray findAll(String pathExpression, int qty)
      A heroically permissive finder supporting JSON Pointer, JSONPath and a simple 'dot and wildcard' type of system like so: 'propName.childPropName.*.skippedGenerationPropsName.4.fifthArrayNodeChildPropsName.**.recursivelyFoundPropsName'.

      All forms are internally converted into a 'master' form before processing. This master simply uses '.' to separate property names and array indexes and uses uses '*' to represent a single level wildcard and '**' to represent a recursive wildcard. For example:

      • 'myProp' finds 'myProp' in this node.
      • 'myProp.childProp' finds 'childProp' on 'myProp'
      • 'myArrayProp.2.*' finds all properties of the third element of the 'myArrayProp'
      • '*.myProp' finds 'myProp' in any of the children of this node.
      • '**.myProp' finds 'myProp' anywhere in my descendents.
      • '**.myProp.*.value' finds 'value' as a grandchild anywhere under me.
      • '**.*' returns every element of the document.
      • '**.5' gets the 6th element of every array.
      • '**.book[?(@.isbn)]' finds all books with an isbn
      • '**.[?(@.author = 'Herman Melville')]' finds all book with author 'Herman Melville'

      Arrays indexes are treated just like property names but with integer names. For example "myObject.4.nextProperty" finds "nextProperty" on the 5th element in the "myObject" array.

      JSON Pointer is the least expressive supported form and uses '/' characters to separate properties. To support JSON Pointer, we simply replace all '/' characters for "." characters before processing.

      JSON Path is more like XML XPath but uses '.' instead of '/' to separate properties. Technically JSON Path statements are supposed to start with '$.' but that is optional here. The best part about JSON Path is the query filters that let you conditionally select elements.

      Below is the implementation status of various JSON Path features:

      • SUPPORTED $.store.book[*].author //the authors of all books in the store
      • SUPPORTED $..author //all authors
      • SUPPORTED $.store..price //the prices of all books
      • SUPPORTED $..book[2] //the third book
      • SUPPORTED $..book[?(@.price@lt;10)] //all books priced @lt; 10
      • SUPPORTED $..[?(@.price@lt;10)] //find any node with a price property
      • SUPPORTED $..[?(@.*.price@lt;10)] //find the parent of any node with a price property
      • SUPPORTED $..book[?(@.author = 'Herman Melville')] //all books where 'Herman Melville' is the author
      • SUPPORTED $..* //all members of JSON structure.
      • SUPPORTED $..book[(@.length-1)] //the last book in order
      • SUPPORTED $..book[-1:] //the last book in order
      • SUPPORTED $..book[0,1] //the first two books
      • SUPPORTED $..book[:2] //the first two books
      • SUPPORTED $..book[?(@.isbn)] //find all books with an isbn property
      • SUPPORTED $..[?(@.isbn)] //find any node with an isbn property
      • SUPPORTED $..[?(@.*.isbn)] //find the parent of any node with an isbn property
      • SUPPORTED $..[?(@.*.*.isbn)] //find the grandparent of any node with an isbn property

      The JSON Path following boolean comparison operators are supported:

      • =
      • @gt;
      • @lt;
      • @gt;=
      • @lt;=
      • !=

      JsonPath bracket-notation such as "$['store']['book'][0]['title']" is currently not supported.

      Parameters:
      pathExpression - defines the properties to find
      qty - the maximum number of results
      Returns:
      an array of found values
      See Also:
    • diff

      public JSArray diff(JSNode source)
    • patch

      public void patch(JSArray patches)
    • get

      public Object get(Object name)
      Specified by:
      get in interface Map<String,Object>
    • getNode

      public JSNode getNode(String name) throws ClassCastException
      Convenience overloading of get(Object)
      Parameters:
      name - the case insensitive property name to retrieve.
      Returns:
      the value of property name cast to a JSNode if exists else null
      Throws:
      ClassCastException - if the object found is not a JSNode
      See Also:
    • getArray

      public JSArray getArray(String name)
      Convenience overloading of get(Object)
      Parameters:
      name - the case insensitive property name to retrieve.
      Returns:
      the value of property name cast to a JSArray if exists else null
      Throws:
      ClassCastException - if the object found is not a JSArray
      See Also:
    • getString

      public String getString(String name)
      Convenience overloading of get(Object)
      Parameters:
      name - the case insensitive property name to retrieve.
      Returns:
      the stringified value of property name if it exists else null
      See Also:
    • getInt

      public int getInt(String name)
      Convenience overloading of get(Object)
      Parameters:
      name - the case insensitive property name to retrieve.
      Returns:
      the value of property name stringified and parsed as an int if it exists else -1
      See Also:
    • getLong

      public long getLong(String name)
      Convenience overloading of get(Object)
      Parameters:
      name - the case insensitive property name to retrieve.
      Returns:
      the value of property name stringified and parsed as long if it exists else -1
      See Also:
    • getDouble

      public double getDouble(String name)
      Convenience overloading of get(Object)
      Parameters:
      name - the case insensitive property name to retrieve.
      Returns:
      the value of property name stringified and parsed as a double if it exists else -1
      See Also:
    • getBoolean

      public boolean getBoolean(String name)
      Convenience overloading of get(Object)
      Parameters:
      name - the case insensitive property name to retrieve.
      Returns:
      the value of property name stringified and parsed as a boolean if it exists else false
      See Also:
    • findNode

      public JSNode findNode(String pathExpression)
      Convenience overloading of find(String)
      Parameters:
      pathExpression - specifies the nodes to find
      Returns:
      the first value found at pathExpression cast as a JSNode if exists else null
      Throws:
      ClassCastException - if the object found is not a JSNode
      See Also:
    • findArray

      public JSArray findArray(String pathExpression)
      Convenience overloading of find(String)
      Parameters:
      pathExpression - specifies the properties to find
      Returns:
      the first value found at pathExpression cast as a JSArray if exists else null
      Throws:
      ClassCastException - if the object found is not a JSArray
      See Also:
    • findString

      public String findString(String pathExpression)
      Convenience overloading of find(String)
      Parameters:
      pathExpression - specifies the properties to find
      Returns:
      the first value found at pathExpression stringified if exists else null
      See Also:
    • findInt

      public int findInt(String pathExpression)
      Convenience overloading of find(String)
      Parameters:
      pathExpression - specifies the properties to find
      Returns:
      the first value found at pathExpression stringified and parsed as an int if exists else -1
      See Also:
    • findLong

      public long findLong(String pathExpression)
      Convenience overloading of find(String)
      Parameters:
      pathExpression - specifies the properties to find
      Returns:
      the first value found at pathExpression stringified and parsed as a long if exists else -1
      See Also:
    • findDouble

      public double findDouble(String pathExpression)
      Convenience overloading of find(String)
      Parameters:
      pathExpression - specifies the properties to find
      Returns:
      the first value found at pathExpression stringified and parsed as a double if exists else -1
      See Also:
    • findBoolean

      public boolean findBoolean(String pathExpression)
      Convenience overloading of find(String)
      Parameters:
      pathExpression - specifies the properties to find
      Returns:
      the first value found at pathExpression stringified and parsed as a boolean if exists else false
      See Also:
    • find

      public Object find(String pathExpression)
      Convenience overloading of findAll(String, int) that returns the first item found
      Parameters:
      pathExpression - specifies the properties to find
      Returns:
      the first item found at pathExpression
      See Also:
    • findAll

      public JSArray findAll(String pathExpression)
      Convenience overloading of findAll(String, int)
      Parameters:
      pathExpression - specifies the properties to find
      Returns:
      all items found for pathExpression
      See Also:
    • findAllNodes

      public List<JSNode> findAllNodes(String pathExpression)
      Convenience overloading of findAll(String, int)
      Parameters:
      pathExpression - specifies the properties to find
      Returns:
      all items found for pathExpression cast as a List
      See Also:
    • put

      public Object put(String name, Object value)
      Specified by:
      put in interface Map<String,Object>
    • putAll

      public void putAll(Map<? extends String,? extends Object> map)
      Specified by:
      putAll in interface Map<String,Object>
    • putFirst

      public Object putFirst(String name, Object value)
      Vanity method to make sure the attributes prints out first
      Parameters:
      name - the property name
      value - the property value
      Returns:
      the previous value of the property if it exists or null
    • with

      public JSNode with(Object... nvPairs)
    • containsKey

      public boolean containsKey(Object name)
      Specified by:
      containsKey in interface Map<String,Object>
    • remove

      public Object remove(Object name)
      Specified by:
      remove in interface Map<String,Object>
    • removeAll

      public Object removeAll(String... names)
      Removes all properties with names.
      Parameters:
      names - the keys to remove
      Returns:
      the first non null value for names
    • keySet

      public Set<String> keySet()
      Specified by:
      keySet in interface Map<String,Object>
    • hasProperty

      public boolean hasProperty(String name)
    • asMap

      public Map<? extends String,? extends Object> asMap()
    • copy

      public JSNode copy()
      Makes a deep copy of this JSNode by stringifying/parsing.
      Returns:
      a deep copy of this node.
    • sortKeys

      public void sortKeys()
      Changes the property name iteration order from insertion order to alphabetic order.
    • isArray

      public boolean isArray()
      Easy alternative to 'instanceof' to differentiate JSNode from JSArray (which subclasses JSNode).
      Returns:
      true if this class is a subclass of JSArray.
      See Also:
    • toString

      public String toString()
      Overrides:
      toString in class Object
      Returns:
      json string, pretty printed with properties written out in their original case.
    • toString

      public String toString(boolean pretty)
      Prints the JSNode with properties written out in their original case.
      Parameters:
      pretty - should spaces and carriage returns be added to the doc for readability
      Returns:
      json string, with properties written out in their original case optional pretty printed.
    • toString

      public String toString(boolean pretty, boolean lowercasePropertyNames)
      Prints the JSNode
      Parameters:
      pretty - should spaces and carriage returns be added to the doc for readability
      lowercasePropertyNames - when true all property names are printed in lower case instead of their original case
      Returns:
      a json string
    • size

      public int size()
      Specified by:
      size in interface Map<String,Object>
      Returns:
      the number of properties on this node.
    • isEmpty

      public boolean isEmpty()
      Specified by:
      isEmpty in interface Map<String,Object>
      Returns:
      true if size() == 0
    • containsValue

      public boolean containsValue(Object value)
      Checks all property values for equality to value
      Specified by:
      containsValue in interface Map<String,Object>
      Returns:
      true if any property values are equal to value
    • clear

      public void clear()
      Removes all properties
      Specified by:
      clear in interface Map<String,Object>
    • values

      public Collection values()
      Specified by:
      values in interface Map<String,Object>
      Returns:
      a collection of property values
    • entrySet

      public Set<Map.Entry<String,Object>> entrySet()
      Specified by:
      entrySet in interface Map<String,Object>
      Returns:
      all property name / value pairs
    • asList

      public List asList()
      Returns this object as the only element in a list.

      JSArray overrides this method to return all of its elements in a list.

      This method is designed to make it super easy to iterate over all property values or array elements without having to cast or consider differences between JSNode and JSArray.

      For example:

       JSNode node = response.getJson();//don't know if this is a JSNode or JSArray
       for(Object value : node.asList())
       {
          //do something;
       }
       
      Returns:
      A List with this node as the only value.
      See Also:
    • asNodeList

      public List<JSNode> asNodeList()
      Returns this object as the only element in a List

      JSArray overrides this method to return all of its elements in a list.

      This method is designed to make it super easy to iterate over all property values or array elements without having to cast or consider differences between JSNode and JSArray.

      For example:

       JSNode node = response.getJson();//don't know if this is a JSNode or JSArray
       for(JSNode child : node.asList())
       {
          System.out.println("found items with price: " + child.find("**.item.price"));
       }
       
      Returns:
      A List with this node as the only value.
      See Also:
    • asArray

      public JSArray asArray()
      Similar to #asList() but instead of returning a List, it returns a this JSNode as the only item in a JSArray.

      JSArray overrides this method to simply return 'this'.

      Returns:
      a JSArray with 'this' as the only element.
      See Also:
    • stream

      public Stream stream()
      Convenience method that calls asList().stream().
      Returns:
      asList().stream()
    • streamAll

      public Stream streamAll()
      Returns:
      a stream of findAll(**.*) plus "this"