Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(WIP) Implement #4958: improve/add scalar accessors to JsonNode [JSTEP-3] #4970

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 40 additions & 22 deletions src/main/java/tools/jackson/databind/JsonNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -469,31 +469,29 @@ public final boolean isBinary() {

/**
* Method that can be used to check whether this node is a numeric
* node ({@link #isNumber} would return true) AND its value fits
* within Java's 32-bit signed integer type, <code>int</code>.
* Note that floating-point numbers are convertible if the integral
* part fits without overflow (as per standard Java coercion rules)
* node ({@link #isNumber} would return true)
* AND can be converted without loss to it (that is, its value fits
* within Java's 32-bit signed integer type, <code>int</code> and
* if it is a floating-point number, it does not have fractional part).
*<p>
* NOTE: this method does not consider possible value type conversion
* from JSON String into Number; so even if this method returns false,
* it is possible that {@link #asInt} could still succeed
* if node is a JSON String representing integral number, or boolean.
* from non-number types like JSON String into Number; so even if this method returns false,
* it is possible that {@link #asInt} could still succeed.
*
* @since 2.0
*/
public boolean canConvertToInt() { return false; }

/**
* Method that can be used to check whether this node is a numeric
* node ({@link #isNumber} would return true) AND its value fits
* within Java's 64-bit signed integer type, <code>long</code>.
* Note that floating-point numbers are convertible if the integral
* part fits without overflow (as per standard Java coercion rules)
* node ({@link #isNumber} would return true)
* AND can be converted without loss to it (that is, its value fits
* within Java's 64-bit signed integer type, <code>long</code> and
* if it is a floating-point number, it does not have fractional part).
*<p>
* NOTE: this method does not consider possible value type conversion
* from JSON String into Number; so even if this method returns false,
* it is possible that {@link #asLong} could still succeed
* if node is a JSON String representing integral number, or boolean.
* from non-number types like JSON String into Number; so even if this method returns false,
* it is possible that {@link #asLong} could still succeed.
*/
public boolean canConvertToLong() { return false; }

Expand Down Expand Up @@ -682,16 +680,36 @@ public boolean asBoolean(boolean defaultValue) {
// // Scalar access: Numbers, Java int

/**
* Returns integer value for this node, <b>if and only if</b>
* this node is numeric ({@link #isNumber} returns true). For other
* types returns 0.
* For floating-point numbers, value is truncated using default
* Java coercion, similar to how cast from double to int operates.
* Method that will try to access value of this node as a Java {@code int}:
* but if node value cannot be expressed <b>exactly</b> as an int,
* a {@link JsonNodeException} will be thrown.
* Access works for following cases:
* <ul>
* <li>JSON Integer values that fit in Java 32-bit signed {@code int} range
* </li>
* <li>JSON Floating-point values that fit in Java 32-bit signed {@code int} range
* AND do not have fractional part.
* </li>
* </ul>
*<p>
* NOTE: for more lenient conversions, use {@link #asInt()}
*
* @return Integer value this node contains, if any; 0 for non-number
* nodes.
* @return Int value this node represents, if possible to accurately represent
*
* @throws JsonNodeException if node cannot be converted to Java {@code int}
*/
public abstract int intValue();

/**
* Method similar to {@link #intValue()}, but that will return specified
* {@code defaultValue} if this node cannot be converted to Java {@code int}.
*
* @param defaultValue Value to return if this node cannot be converted to Java {@code int}
*
* @return Java {@code int} value this node represents, if possible to accurately represent;
* {@code defaultValue} otherwise
*/
public int intValue() { return 0; }
public abstract int intValue(int defaultValue);

/**
* Method that will try to convert value of this node to a Java <b>int</b>.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package tools.jackson.databind.deser.jdk;

import java.io.IOException;

import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.deser.std.StdNodeBasedDeserializer;
Expand Down
21 changes: 19 additions & 2 deletions src/main/java/tools/jackson/databind/node/ArrayNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@ public class ArrayNode

private final List<JsonNode> _children;

/*
/**********************************************************************
/* Construction
/**********************************************************************
*/

public ArrayNode(JsonNodeFactory nf) {
super(nf);
_children = new ArrayList<JsonNode>();
_children = new ArrayList<>();
}

public ArrayNode(JsonNodeFactory nf, int capacity) {
super(nf);
_children = new ArrayList<JsonNode>(capacity);
_children = new ArrayList<>(capacity);
}

public ArrayNode(JsonNodeFactory nf, List<JsonNode> children) {
Expand All @@ -43,11 +49,22 @@ public ArrayNode(JsonNodeFactory nf, List<JsonNode> children) {
"Must not pass `null` for 'children' argument");
}

/*
/**********************************************************************
/* Overridden JsonNode methods
/**********************************************************************
*/

@Override
protected JsonNode _at(JsonPointer ptr) {
return get(ptr.getMatchingIndex());
}

@Override
protected String _valueDesc() {
return "[...(" + _children.size() + " elements)]";
}

// note: co-variant to allow caller-side type safety
@Override
public ArrayNode deepCopy()
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/tools/jackson/databind/node/BaseJsonNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,23 @@ protected BaseJsonNode() { }
@Override
public boolean isEmbeddedValue() { return false; }

/*
/**********************************************************************
/* Defaulting for scalar access
/**********************************************************************
*/

@Override
public int intValue() {
return _reportCoercionFail("intValue()", Integer.TYPE, "value type not numeric");
}

@Override
public int intValue(int defaultValue) {
// Overridden by NumericNode, for other types return default
return defaultValue;
}

/*
/**********************************************************************
/* Basic definitions for non-container types
Expand Down Expand Up @@ -261,6 +278,14 @@ public String toPrettyString() {
/**********************************************************************
*/

protected <T> T _reportCoercionFail(String method, Class<?> targetType,
String message)
{
throw JsonNodeException.from(this, "'%s' method `%s` cannot convert value %s to %s: %s",
getClass().getSimpleName(), method,
_valueDesc(), ClassUtil.nameOf(targetType), message);
}

/**
* Helper method that throws {@link JsonNodeException} as a result of
* this node being of wrong type
Expand All @@ -286,4 +311,10 @@ protected JsonPointer _jsonPointerIfValid(String exprOrProperty) {
}
return null;
}

/**
* Method for implementation classes to return a short description of contained
* value, to be used in error messages.
*/
protected abstract String _valueDesc();
}
37 changes: 30 additions & 7 deletions src/main/java/tools/jackson/databind/node/BigIntegerNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ public BigIntegerNode(BigInteger v) {
public static BigIntegerNode valueOf(BigInteger v) { return new BigIntegerNode(v); }

/*
/**********************************************************
/* Overridden JsonNode methods
/**********************************************************
/**********************************************************************
/* Overridden JsonNode methods, simple properties
/**********************************************************************
*/

@Override
Expand All @@ -47,19 +47,31 @@ public BigIntegerNode(BigInteger v) {
@Override
public JsonParser.NumberType numberType() { return JsonParser.NumberType.BIG_INTEGER; }

@Override
public boolean isBigInteger() { return true; }

@Override
public boolean isIntegralNumber() { return true; }

@Override
public boolean isBigInteger() { return true; }
public boolean isNaN() { return false; }

@Override public boolean canConvertToInt() {
return (_value.compareTo(MIN_INTEGER) >= 0) && (_value.compareTo(MAX_INTEGER) <= 0);
return (_value.compareTo(MIN_INTEGER) >= 0)
&& (_value.compareTo(MAX_INTEGER) <= 0);
}

@Override public boolean canConvertToLong() {
return (_value.compareTo(MIN_LONG) >= 0) && (_value.compareTo(MAX_LONG) <= 0);
return (_value.compareTo(MIN_LONG) >= 0)
&& (_value.compareTo(MAX_LONG) <= 0);
}

/*
/**********************************************************************
/* Overridden JsonNode methods, scalar access
/**********************************************************************
*/

@Override
public Number numberValue() {
return _value;
Expand All @@ -69,7 +81,18 @@ public Number numberValue() {
public short shortValue() { return _value.shortValue(); }

@Override
public int intValue() { return _value.intValue(); }
public int intValue() {
if (canConvertToInt()) {
return _value.intValue();
}
return _reportCoercionFail("intValue()", Integer.TYPE,
"value not in 32-bit `int` range");
}

@Override
public int intValue(int defaultValue) {
return canConvertToInt() ? _value.intValue() : defaultValue;
}

@Override
public long longValue() { return _value.longValue(); }
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/tools/jackson/databind/node/BinaryNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ public class BinaryNode

protected final byte[] _data;

/*
/**********************************************************************
/* Construction
/**********************************************************************
*/

public BinaryNode(byte[] data)
{
// 01-Mar-2024, tatu: [databind#4381] No null-valued JsonNodes
Expand Down Expand Up @@ -56,6 +62,12 @@ public static BinaryNode valueOf(byte[] data, int offset, int length)
return new BinaryNode(data, offset, length);
}

/*
/**********************************************************************
/* Overridden JsonNode methods
/**********************************************************************
*/

@Override
public JsonNodeType getNodeType()
{
Expand All @@ -71,6 +83,11 @@ public JsonToken asToken() {
return JsonToken.VALUE_EMBEDDED_OBJECT;
}

@Override
protected String _valueDesc() {
return "[...(" + _data.length + " bytes)]";
}

/**
*<p>
* Note: caller is not to modify returned array in any way, since
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/tools/jackson/databind/node/BooleanNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected Object readResolve() {

/*
/**********************************************************************
/* Overrridden JsonNode methods
/* Overridden JsonNode methods
/**********************************************************************
*/

Expand All @@ -53,6 +53,11 @@ public JsonNodeType getNodeType() {
return _value ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE;
}

@Override
protected String _valueDesc() {
return asString();
}

@Override
public BooleanNode deepCopy() { return this; }

Expand Down
Loading