Skip to content

Jackson3 Changes JsonNode

Tatu Saloranta edited this page Jan 27, 2019 · 14 revisions

Jackson 3 changes: JsonNode

Improvements in 2.10

Before listing proposes changes for 3.0, some preliminary work will already be include in 2.10:

  • JsonNode.toString() now DOES guarantee valid JSON output (assuming default vanilla ObjectMapper settings)
  • JsonNode.isEmpty() works as alias for idiom size() == 0

Configurability

As of Jackson 2.x, only some of DeserializationFeatures (and few if any SerializationFeatures) affect handling of JsonNode. This is due to most of them being POJO-centric, as JsonNode is meant to be faithful representation of JSON that was read, or is to be written out, with few changes.

But users do have legitimate need/desire to make SOME changes. It's just that these changes are not necessarily aligned with changes to POJO handling.

With that, I suggest addition of...

NodeFeature

This would be enumeration like DeserializationFeature with choices like:

  • READ_NULL_PROPERTIES (default: true) -- are null valued properties represented as NullNodes in resulting JsonObject or skipped?
  • WRITE_NULL_PROPERTIES (default: true) -- are null valued fields in ObjectNode written out as JSON or skipped?
  • READ_NULL_ELEMENTS (default: true) -- are null elements in JSON arrays represented as NullNodes in resulting JsonArray or skipped?
  • WRITE_NULL_ELEMENTS (default: true) -- are null elements in ArrayNode written out as JSON or skipped?
  • CONVERT_POJOS_FULLY (default: ?) -- when converting values (mapper.convertValue(), treeToValue()), are opaque values contained in POJONode:
    1. Serialized explicitly by matching serializer (true) -- which will essentially transform it into non-opaque value (like Map, List etc)
    2. Written out as "writePOJO", which in case of TokenBuffer will retain opaque value exactly as is
  • LENIENT_SCALAR_CONVERSION (default: false?): do we allow more speculative coercion, like boolean from int (zero -> false, otherwise true)
  • TRIM_DECIMAL_TRAILING_ZEROES (default: true?): do we force dropping of trailing zeroes for BigDecimal valued nodes?

One thing to note is that while there could be NodeReadFeature/NodeWriteFeature, I think set of values we need is small enough, and things closely related so that it makes more sense to have just one set. It is also bit easier to implement.

Exception reporting

With 3.0, plan is to change JsonProcessingException to be unchecked (see databind#2177)

This is relevant for JsonNode, too, since we can start throwing formal Jackson exceptions, without having to declare them separately for all (or just some) accessors. This is relevant for a few entries here:

  • JsonNode.toString() can pass any exceptions for real serialization as-is, with no additional wrapping

However: I think it also makes sense to introduce at least one new Jackson exception type, like:

  • ValueCoercionException

which can then be used by methods that attempt coercion (like JsonNode.asIntValue()), but fail due to incompatible types (or perhaps parsing error, from String to number). This exception type should retain and expose information like:

  • Source token type (JsonToken.VALUE_STRING)
  • Target shape (as token, like JsonToken.VALUE_NUMBER_INT)
  • If available, target Java type (java.lang.Integer#type for int, for example)
  • We do not necessarily have information on path, but possibly exception catch/rethrow could re-create this

Additional traversal, mutation

Removal

  • For ContainerNode (JsonObject, JsonArray):
    • removeNullValues() (or: separate for object ("removeNullProperties()") vs array ("removeNullElements")?)
    • clear() (remove all entries)

Traversal

It should be possible to stream through both values (array elements, values of properties), and in case of JsonObject, entries (name and value). Not sure what is the best Java 8 pattern to follow, perhaps take consumers?

Transformation

Maybe allow a way to "map"? Might become problematic for JsonObject, if and when modifying while traversing: maybe should just create a new JsonObject / JsonArray if and when making any change(s)?

Will definitely need help defining good Java 8 API additions here

Extended set of scalar value accessors

As of 2.x, there are a few accessors for type like, say, int:

  • intValue(): return int if (and only if!) we got numeric integer node (JsonToken.VALUE_NUMBER_INT) -- but won't throw exception, returns default value (0) otherwise
  • asInt(): same as asInt(0)
  • asInt(int defaultValue): return int value from JsonToken.VALUE_NUMBER_INT (if within value range) OR if coercible (from double or String); otherwise return defaultValue

But none actually throws exception: partly due to historical reasons (was not done initially), and also since methods do not expose JsonProcessingException (or IOException). So, due to backwards compatibility.

So there are couple of things to improve:

  1. Should allow throwing of exceptions, for case where no default is specified. We can now do this more easily as we throw unchecked exception -- meaning accessors are still safe with Java 8 streaming
  2. Should allow use of Java 8 Optional, as that is useful for stream() operations (and now we can use Java 8 features)

This would leave to bigger set of methods, once again for int:

  1. intValue() as before, return value if JsonToken.VALUE_NUMBER_INT, within Java int range -- but if not, throw exception
  2. intValue(int defaultValue) as intValue() except returns defaultValue instead of exception
  3. optIntValue() similar to intValue() but returns OptionalInt, either present (as per intValue()) or absent (instead of exception)
  4. asInt() (or asIntValue()?) similar to intValue() but allows coercion from double and String (and possible with config, from true/false). But if not, throw exception
  5. asInt(int defaultValue) like asInt() but instead of exception, return defaultValue() for case of no valid value
  6. asOptInt() (maybe?): similar to asInt() but instead of exception, return "absent" value

Now: set of operations may be smaller for other more esoteric types. We should also have generic

  • opt(String / int)

as counterpart to get() / path(), which would return Optional<JsonNode>. And could consider:

  • required(String / int) (or req()?) for basically "get, throw exception if no node with name/index)

In fact, we could consider further JsonNode method(s) of:

  • require() -- returns JsonNode (or subtype, co-variant) unless MissingNode; if MissingNode, throw exception (unchecked, Jackson-specific)
  • requireNonNull() (or requirePresent()? -- same as require(), but fail both for MissingNode and NullNode

although, with exceptions from intValue() (etc), are these needed? Not much to add, for sure, but while there are already many, many methods, I like to avoid adding useless methods where we can.

Misc coercion, checking?

Is there need for casting with something like

  • asObject()? (requires forward-ref to subtype, not really optimal -- although could maybe use local type bound of ` -- need to test)
  • similarly, asArray()?

Misc other

  • Rename methods with "text" to use "string", similarly types ("TextNode" -> "StringNode"): deprecate old methods in cases where significant usage exists (textValue(), asText())
  • "Bulk" add methods for scalars? ("addAll()", "putAll()") -- and maybe just for simple things like String, int, long values?
    • Maybe via JsonNodeFactory?

Clone this wiki locally