Skip to content

Commit eb15603

Browse files
authored
Fix #5034: re-write JsonNode.asBoolean(), .asString(), variants (#5042)
1 parent 67311ef commit eb15603

25 files changed

+559
-249
lines changed

release-notes/VERSION

+2
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ Versions: 3.x (for earlier see VERSION-2.x)
111111
#5025: Add support for automatic detection of subtypes (like `@JsonSubTypes`)
112112
from Java 17 sealed types
113113
(contributed by Andy B
114+
#5034: Extend, improve set of `JsonNode.asXxx()` methods for non-number
115+
types (Boolean, String)
114116
- Remove `MappingJsonFactory`
115117
- Add context parameter for `TypeSerializer` contextualization (`forProperty()`)
116118
- Default for `JsonNodeFeature.STRIP_TRAILING_BIGDECIMAL_ZEROES` changed to `false` for 3.0

src/main/java/tools/jackson/databind/JsonNode.java

+36-26
Original file line numberDiff line numberDiff line change
@@ -584,27 +584,35 @@ public final String textValue() {
584584
}
585585

586586
/**
587-
* Method that will return a valid String representation of
588-
* the contained value, if the node is a value node
589-
* (method {@link #isValueNode} returns true),
590-
* otherwise empty String.
587+
* Method that will try to convert value of this node to a {@code String}.
588+
* JSON Strings map naturally; other scalars map to their string representation
589+
* (including Binary data as Base64 encoded String);
590+
* JSON {@code null}s map to empty String.
591+
* Other values (including structured types like Objects and Arrays, and "missing"
592+
* value) will result in a {@link JsonNodeException} being thrown.
591593
*<p>
592594
* NOTE: this is NOT same as {@link #toString()} in that result is
593-
* <p>NOT VALID ENCODED JSON</p> for all nodes (but is for some, like
595+
* <p>NOT VALID ENCODED JSON</p> for all nodes (although is for some, like
594596
* {@code NumberNode}s and {@code BooleanNode}s).
597+
*
598+
* @return String representation of this node, if coercible; exception otherwise
599+
*
600+
* @throws JsonNodeException if node cannot be coerced to a {@code String}
595601
*/
596602
public abstract String asString();
597603

598604
/**
599-
* Returns the text value of this node or the provided {@code defaultValue} if this node
600-
* does not have a text value. Useful for nodes that are {@link MissingNode} or
601-
* {@link tools.jackson.databind.node.NullNode}, ensuring a default value is returned instead of null or missing indicators.
602-
*
603-
* @param defaultValue The default value to return if this node's text value is absent.
604-
* @return The text value of this node, or {@code defaultValue} if the text value is absent.
605+
* Similar to {@link #asString()}, but instead of throwing an exception for
606+
* non-coercible values, will return specified default value.
605607
*/
606608
public abstract String asString(String defaultValue);
607609

610+
/**
611+
* Similar to {@link #asString()}, but instead of throwing an exception for
612+
* non-coercible values, will return {@code Optional.empty()}.
613+
*/
614+
public abstract Optional<String> asStringOpt();
615+
608616
/**
609617
* @deprecated Use {@link #asString()} instead.
610618
*/
@@ -672,29 +680,31 @@ public String asText(String defaultValue) {
672680
public abstract Optional<Boolean> booleanValueOpt();
673681

674682
/**
675-
* Method that will try to convert value of this node to a Java <b>boolean</b>.
676-
* JSON booleans map naturally; integer numbers other than 0 map to true, and
677-
* 0 maps to false
683+
* Method that will try to convert value of this node to a Java {@code boolean}.
684+
* JSON Booleans map naturally; Integer numbers other than 0 map to true, and
685+
* 0 maps to false; {@code null} maps to false
678686
* and Strings 'true' and 'false' map to corresponding values.
679-
*<p>
680-
* If representation cannot be converted to a boolean value (including structured types
681-
* like Objects and Arrays),
682-
* default value of <b>false</b> will be returned; no exceptions are thrown.
687+
* Other values (including structured types like Objects and Arrays) will
688+
* result in a {@link JsonNodeException} being thrown.
689+
*
690+
* @return Boolean value this node represents, if coercible; exception otherwise
691+
*
692+
* @throws JsonNodeException if node cannot be coerced to a Java {@code boolean}
683693
*/
684694
public abstract boolean asBoolean();
685695

686696
/**
687-
* Method that will try to convert value of this node to a Java <b>boolean</b>.
688-
* JSON booleans map naturally; integer numbers other than 0 map to true, and
689-
* 0 maps to false
690-
* and Strings 'true' and 'false' map to corresponding values.
691-
*<p>
692-
* If representation cannot be converted to a boolean value (including structured types
693-
* like Objects and Arrays),
694-
* specified <b>defaultValue</b> will be returned; no exceptions are thrown.
697+
* Similar to {@link #asBoolean()}, but instead of throwing an exception for
698+
* non-coercible values, will return specified default value.
695699
*/
696700
public abstract boolean asBoolean(boolean defaultValue);
697701

702+
/**
703+
* Similar to {@link #asBoolean()}, but instead of throwing an exception for
704+
* non-coercible values, will return {@code Optional.empty()}.
705+
*/
706+
public abstract Optional<Boolean> asBooleanOpt();
707+
698708
// // Scalar access: Numbers, generic
699709

700710
/**

src/main/java/tools/jackson/databind/deser/jdk/ThreadGroupDeserializer.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@ protected ThreadGroupDeserializer() {
1919

2020
@Override
2121
public ThreadGroup convert(JsonNode root, DeserializationContext ctxt) {
22-
String name = root.path("name").asString();
23-
if (name == null) {
24-
name = "";
25-
}
22+
String name = root.path("name").asString("");
2623
return new ThreadGroup(name);
2724
}
2825
}

src/main/java/tools/jackson/databind/node/BaseJsonNode.java

+79-7
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public abstract class BaseJsonNode
3232
{
3333
private static final long serialVersionUID = 3L;
3434

35+
protected final static Optional<Boolean> OPT_FALSE = Optional.of(false);
36+
protected final static Optional<Boolean> OPT_TRUE = Optional.of(true);
37+
3538
// Simplest way is by using a helper
3639
Object writeReplace() {
3740
return NodeSerialization.from(this);
@@ -199,7 +202,8 @@ public byte[] binaryValue() {
199202

200203
@Override
201204
public boolean booleanValue() {
202-
return _reportCoercionFail("booleanValue()", Boolean.TYPE, "value type not boolean");
205+
return _reportCoercionFail("booleanValue()", Boolean.TYPE,
206+
"value type not boolean");
203207
}
204208

205209
@Override
@@ -216,17 +220,36 @@ public Optional<Boolean> booleanValueOpt() {
216220

217221
@Override
218222
public boolean asBoolean() {
219-
return asBoolean(false);
223+
Boolean b = _asBoolean();
224+
if (b == null) {
225+
return _reportCoercionFail("asBoolean()", Boolean.TYPE,
226+
"value type not coercible to `boolean`");
227+
}
228+
return b;
220229
}
221230

222231
@Override
223232
public boolean asBoolean(boolean defaultValue) {
224-
return defaultValue;
233+
Boolean b = _asBoolean();
234+
if (b == null) {
235+
return defaultValue;
236+
}
237+
return b;
238+
}
239+
240+
@Override
241+
public Optional<Boolean> asBooleanOpt() {
242+
Boolean b = _asBoolean();
243+
if (b == null) {
244+
return Optional.empty();
245+
}
246+
return b.booleanValue() ? OPT_TRUE : OPT_FALSE;
225247
}
226248

227249
@Override
228250
public String stringValue() {
229-
return _reportCoercionFail("stringValue()", String.class, "value type not String");
251+
return _reportCoercionFail("stringValue()", String.class,
252+
"value type not String");
230253
}
231254

232255
@Override
@@ -241,10 +264,28 @@ public Optional<String> stringValueOpt() {
241264
return Optional.empty();
242265
}
243266

267+
@Override
268+
public String asString() {
269+
String str = _asString();
270+
if (str == null) {
271+
return _reportCoercionFail("asString()", String.class,
272+
"value type not coercible to `String`");
273+
}
274+
return str;
275+
}
276+
244277
@Override
245278
public String asString(String defaultValue) {
246-
String str = asString();
247-
return (str == null) ? defaultValue : str;
279+
String str = _asString();
280+
if (str == null) {
281+
return defaultValue;
282+
}
283+
return str;
284+
}
285+
286+
@Override
287+
public Optional<String> asStringOpt() {
288+
return Optional.ofNullable(_asString());
248289
}
249290

250291
/*
@@ -264,7 +305,8 @@ public final JsonNode findPath(String fieldName)
264305
}
265306

266307
// Also, force (re)definition
267-
@Override public abstract int hashCode();
308+
@Override
309+
public abstract int hashCode();
268310

269311
/*
270312
/**********************************************************************
@@ -417,6 +459,36 @@ protected ArrayNode _withArray(JsonPointer origPtr,
417459
return null;
418460
}
419461

462+
/*
463+
/**********************************************************************
464+
/* asXxx() helper methods for sub-classes to implement
465+
/**********************************************************************
466+
*/
467+
468+
/**
469+
* Method sub-classes should override if they can produce {@code boolean}
470+
* values via {@link #asBoolean()} -- if not, return {@code null} (in which
471+
* case appropriate error will be thrown or default value returned).
472+
*
473+
* @return Coerced value if possible; otherwise {@code null} to indicate this
474+
* node cannot be coerced.
475+
*/
476+
protected Boolean _asBoolean() {
477+
return null;
478+
}
479+
480+
/**
481+
* Method sub-classes should override if they can produce {@code String}
482+
* values via {@link #asString()} -- if not, return {@code null} (in which
483+
* case appropriate error will be thrown or default value returned).
484+
*
485+
* @return Coerced value if possible; otherwise {@code null} to indicate this
486+
* node cannot be coerced.
487+
*/
488+
protected String _asString() {
489+
return null;
490+
}
491+
420492
/*
421493
/**********************************************************************
422494
/* JacksonSerializable

src/main/java/tools/jackson/databind/node/BigIntegerNode.java

+11-17
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,17 @@ public BigIntegerNode(BigInteger v) {
7575
/* Overridden JsonNode methods, scalar access
7676
/**********************************************************************
7777
*/
78-
78+
79+
@Override
80+
protected Boolean _asBoolean() {
81+
return !BigInteger.ZERO.equals(_value);
82+
}
83+
84+
@Override
85+
public String _asString() {
86+
return _value.toString();
87+
}
88+
7989
@Override
8090
public Number numberValue() {
8191
return _value;
@@ -185,22 +195,6 @@ public Optional<BigDecimal> decimalValueOpt() {
185195
return Optional.of(new BigDecimal(_value));
186196
}
187197

188-
/*
189-
/**********************************************************
190-
/* General type coercions
191-
/**********************************************************
192-
*/
193-
194-
@Override
195-
public String asString() {
196-
return _value.toString();
197-
}
198-
199-
@Override
200-
public boolean asBoolean(boolean defaultValue) {
201-
return !BigInteger.ZERO.equals(_value);
202-
}
203-
204198
/*
205199
/**********************************************************
206200
/* Other overrides

src/main/java/tools/jackson/databind/node/BinaryNode.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ protected String _valueDesc() {
107107
* but will work correctly.
108108
*/
109109
@Override
110-
public String asString() {
110+
protected String _asString() {
111111
return Base64Variants.getDefaultVariant().encode(_data, false);
112112
}
113113

src/main/java/tools/jackson/databind/node/BooleanNode.java

+22-13
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ public class BooleanNode
1919

2020
public final static BooleanNode TRUE = new BooleanNode(true);
2121
public final static BooleanNode FALSE = new BooleanNode(false);
22-
23-
private final static Optional<Boolean> OPT_FALSE = Optional.of(false);
24-
private final static Optional<Boolean> OPT_TRUE = Optional.of(true);
2522

2623
private final boolean _value;
2724

@@ -72,31 +69,48 @@ protected String _valueDesc() {
7269
/**********************************************************************
7370
*/
7471

72+
// // // Override "asBoolean()" methods as minor optimization
73+
7574
@Override
76-
public boolean booleanValue() {
75+
public final boolean asBoolean() {
7776
return _value;
7877
}
7978

8079
@Override
81-
public boolean booleanValue(boolean defaultValue) {
80+
public final boolean asBoolean(boolean defaultValue) {
8281
return _value;
8382
}
8483

8584
@Override
86-
public Optional<Boolean> booleanValueOpt() {
85+
public Optional<Boolean> asBooleanOpt() {
8786
return _value ? OPT_TRUE : OPT_FALSE;
8887
}
88+
89+
@Override
90+
protected Boolean _asBoolean() {
91+
return _value;
92+
}
8993

9094
@Override
91-
public boolean asBoolean() {
95+
public boolean booleanValue() {
9296
return _value;
9397
}
9498

9599
@Override
96-
public boolean asBoolean(boolean defaultValue) {
100+
public boolean booleanValue(boolean defaultValue) {
97101
return _value;
98102
}
99103

104+
@Override
105+
public Optional<Boolean> booleanValueOpt() {
106+
return _value ? OPT_TRUE : OPT_FALSE;
107+
}
108+
109+
@Override
110+
protected String _asString() {
111+
return _value ? "true" : "false";
112+
}
113+
100114
@Override
101115
public int asInt(int defaultValue) {
102116
return _value ? 1 : 0;
@@ -110,11 +124,6 @@ public double asDouble(double defaultValue) {
110124
return _value ? 1.0 : 0.0;
111125
}
112126

113-
@Override
114-
public String asString() {
115-
return _value ? "true" : "false";
116-
}
117-
118127
/*
119128
/**********************************************************************
120129
/* Overridden JsonNode methods, other

src/main/java/tools/jackson/databind/node/ContainerNode.java

-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ public boolean isContainer() {
4343
@Override
4444
public abstract JsonToken asToken();
4545

46-
@Override
47-
public String asString() { return ""; }
48-
4946
/*
5047
/**********************************************************************
5148
/* Methods reset as abstract to force real implementation

0 commit comments

Comments
 (0)