Skip to content

Commit 0a2c836

Browse files
authored
Fix #4991: add/update JsonNode.xxxValue() for non-number scalars (#5007)
1 parent 030a829 commit 0a2c836

14 files changed

+360
-57
lines changed

release-notes/VERSION

+2
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ Versions: 3.x (for earlier see VERSION-2.x)
100100
#4956: Rename `JsonNode.isContainerNode()` as `isContainerNode()`
101101
#4958: Extend, improve set of number value accessors for `JsonNode`
102102
(`JsonNode.intValue()` etc) [JSTEP-3]
103+
#4991: Extend, improve set of non-number scalar value accessors for `JsonNode`
104+
(`JsonNode.booleanValue()` etc) [JSTEP-3]
103105
#4992: Rename `JsonNodeFactory.textNode()` as `JsonNodeFactory.stringNode()` [JSTEP-3]
104106
#5004: Add `JsonMapper.builderWithJackson2Defaults()`
105107
(fixed by @pjfanning)

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

+73-20
Original file line numberDiff line numberDiff line change
@@ -541,18 +541,47 @@ public Optional<JsonNode> asOptional() {
541541
// // Scalar access: Strings
542542

543543
/**
544-
* Method to use for accessing String values.
545-
* Does <b>NOT</b> do any conversions for non-String value nodes;
546-
* for non-String values (ones for which {@link #isString} returns
547-
* false) null will be returned.
548-
* For String values, null is never returned (but empty Strings may be)
544+
* Method that will try to access value of this node as a Java {@code String}
545+
* which works if (and only if) node contains JSON String value:
546+
* if not, a {@link JsonNodeException} will be thrown.
547+
*<p>
548+
* NOTE: for more lenient conversions, use {@link #asString()}
549549
*<p>
550550
* NOTE: in Jackson 2.x, was {@code textValue()}.
551551
*
552-
* @return String value this node contains, iff node is created from
553-
* a String value.
552+
* @return {@code String} value this node represents (if JSON String)
553+
*
554+
* @throws JsonNodeException if node value is not a JSON String value
554555
*/
555-
public String stringValue() { return null; }
556+
public abstract String stringValue();
557+
558+
/**
559+
* Method similar to {@link #stringValue()}, but that will return specified
560+
* {@code defaultValue} if this node does not contain a JSON String.
561+
*
562+
* @param defaultValue Value to return if this node does not contain a JSON String.
563+
*
564+
* @return Java {@code String} value this node represents (if JSON String);
565+
* {@code defaultValue} otherwise
566+
*/
567+
public abstract String stringValue(String defaultValue);
568+
569+
/**
570+
* Method similar to {@link #stringValue()}, but that will return
571+
* {@code Optional.empty()} if this node does not contain a JSON String.
572+
*
573+
* @return {@code Optional<String>} value (if node represents JSON String);
574+
* {@code Optional.empty()} otherwise
575+
*/
576+
public abstract Optional<String> stringValueOpt();
577+
578+
/**
579+
* @deprecated Use {@link #asString()} instead.
580+
*/
581+
@Deprecated // since 3.0
582+
public final String textValue() {
583+
return stringValue();
584+
}
556585

557586
/**
558587
* Method that will return a valid String representation of
@@ -595,29 +624,53 @@ public String asText(String defaultValue) {
595624
// // Scalar access: Binary
596625

597626
/**
598-
* Method to use for accessing binary content of binary nodes (nodes
599-
* for which {@link #isBinary} returns true); or for String Nodes
600-
* (ones for which {@link #stringValue} returns non-null value),
601-
* to read decoded base64 data.
602-
* For other types of nodes, returns null.
627+
* Method that will try to access value of this node as binary value (Java {@code byte[]})
628+
* which works if (and only if) node contains binary value (for JSON, Base64-encoded
629+
* String, for other formats native binary value): if not,
630+
* a {@link JsonNodeException} will be thrown.
631+
* To check if this method can be used, you may call {@link #isBinary()}.
632+
*<p>
633+
* @return Binary value this node represents (if node contains binary value)
603634
*
604-
* @return Binary data this node contains, iff it is a binary
605-
* node; null otherwise
635+
* @throws JsonNodeException if node does not contain a Binary value (a
606636
*/
607637
public abstract byte[] binaryValue();
608638

609639
// // Scalar access: Boolean
610640

611641
/**
612-
* Method to use for accessing JSON boolean values (value
613-
* literals 'true' and 'false').
614-
* For other types, always returns false.
642+
* Method that will try to access value of this node as a Java {@code boolean}
643+
* which works if (and only if) node contains JSON boolean value: if not,
644+
* a {@link JsonNodeException} will be thrown.
645+
*<p>
646+
* NOTE: for more lenient conversions, use {@link #asBoolean()}
647+
*
648+
* @return {@code boolean} value this node represents (if JSON boolean)
615649
*
616-
* @return Boolean value this node contains, if any; false for
617-
* non-boolean nodes.
650+
* @throws JsonNodeException if node does not represent a JSON boolean value
618651
*/
619652
public abstract boolean booleanValue();
620653

654+
/**
655+
* Method similar to {@link #booleanValue()}, but that will return specified
656+
* {@code defaultValue} if this node does not contain a JSON boolean.
657+
*
658+
* @param defaultValue Value to return if this node does not contain a JSON boolean.
659+
*
660+
* @return Java {@code boolean} value this node represents (if JSON boolean);
661+
* {@code defaultValue} otherwise
662+
*/
663+
public abstract boolean booleanValue(boolean defaultValue);
664+
665+
/**
666+
* Method similar to {@link #booleanValue()}, but that will return
667+
* {@code Optional.empty()} if this node does not contain a JSON boolean.
668+
*
669+
* @return {@code Optional<Boolean>} value (if node represents JSON boolean);
670+
* {@code Optional.empty()} otherwise
671+
*/
672+
public abstract Optional<Boolean> booleanValueOpt();
673+
621674
/**
622675
* Method that will try to convert value of this node to a Java <b>boolean</b>.
623676
* JSON booleans map naturally; integer numbers other than 0 map to true, and

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

+32-2
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,25 @@ public BigDecimal asDecimal(BigDecimal defaultValue) {
193193

194194
@Override
195195
public byte[] binaryValue() {
196-
return null;
196+
return _reportCoercionFail("binaryValue()", Boolean.TYPE,
197+
"value type not binary (or convertible to binary via Base64-decoding)");
197198
}
198199

199200
@Override
200201
public boolean booleanValue() {
201-
return false;
202+
return _reportCoercionFail("booleanValue()", Boolean.TYPE, "value type not boolean");
203+
}
204+
205+
@Override
206+
public boolean booleanValue(boolean defaultValue) {
207+
// Overridden by BooleanNode, for other types return default
208+
return defaultValue;
209+
}
210+
211+
@Override
212+
public Optional<Boolean> booleanValueOpt() {
213+
// Overridden by BooleanNode, for other types return default
214+
return Optional.empty();
202215
}
203216

204217
@Override
@@ -211,6 +224,23 @@ public boolean asBoolean(boolean defaultValue) {
211224
return defaultValue;
212225
}
213226

227+
@Override
228+
public String stringValue() {
229+
return _reportCoercionFail("stringValue()", String.class, "value type not String");
230+
}
231+
232+
@Override
233+
public String stringValue(String defaultValue) {
234+
// Overridden by StringNode, for other types return default
235+
return defaultValue;
236+
}
237+
238+
@Override
239+
public Optional<String> stringValueOpt() {
240+
// Overridden by StringNode, for other types return default
241+
return Optional.empty();
242+
}
243+
214244
@Override
215245
public String asString(String defaultValue) {
216246
String str = asString();

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,13 @@ public JsonToken asToken() {
8787
protected String _valueDesc() {
8888
return "[...(" + _data.length + " bytes)]";
8989
}
90-
90+
91+
/*
92+
/**********************************************************************
93+
/* Overridden JsonNode methods, scalar access
94+
/**********************************************************************
95+
*/
96+
9197
/**
9298
*<p>
9399
* Note: caller is not to modify returned array in any way, since
@@ -105,6 +111,12 @@ public String asString() {
105111
return Base64Variants.getDefaultVariant().encode(_data, false);
106112
}
107113

114+
/*
115+
/**********************************************************************
116+
/* Overridden JsonNode methods, other
117+
/**********************************************************************
118+
*/
119+
108120
@Override
109121
public final void serialize(JsonGenerator g, SerializationContext provider)
110122
throws JacksonException

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

+29-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package tools.jackson.databind.node;
22

3+
import java.util.Optional;
4+
35
import tools.jackson.core.*;
46
import tools.jackson.databind.SerializationContext;
57

@@ -18,6 +20,9 @@ public class BooleanNode
1820
public final static BooleanNode TRUE = new BooleanNode(true);
1921
public final static BooleanNode FALSE = new BooleanNode(false);
2022

23+
private final static Optional<Boolean> OPT_FALSE = Optional.of(false);
24+
private final static Optional<Boolean> OPT_TRUE = Optional.of(true);
25+
2126
private final boolean _value;
2227

2328
/*
@@ -61,14 +66,25 @@ protected String _valueDesc() {
6166
@Override
6267
public BooleanNode deepCopy() { return this; }
6368

69+
/*
70+
/**********************************************************************
71+
/* Overridden JsonNode methods, scalar access
72+
/**********************************************************************
73+
*/
74+
6475
@Override
6576
public boolean booleanValue() {
6677
return _value;
6778
}
6879

6980
@Override
70-
public String asString() {
71-
return _value ? "true" : "false";
81+
public boolean booleanValue(boolean defaultValue) {
82+
return _value;
83+
}
84+
85+
@Override
86+
public Optional<Boolean> booleanValueOpt() {
87+
return _value ? OPT_TRUE : OPT_FALSE;
7288
}
7389

7490
@Override
@@ -94,6 +110,17 @@ public double asDouble(double defaultValue) {
94110
return _value ? 1.0 : 0.0;
95111
}
96112

113+
@Override
114+
public String asString() {
115+
return _value ? "true" : "false";
116+
}
117+
118+
/*
119+
/**********************************************************************
120+
/* Overridden JsonNode methods, other
121+
/**********************************************************************
122+
*/
123+
97124
@Override
98125
public final void serialize(JsonGenerator g, SerializationContext provider)
99126
throws JacksonException {

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

+6
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,12 @@ public boolean asBoolean(boolean defaultValue) {
150150
return _value != 0;
151151
}
152152

153+
/*
154+
/**********************************************************************
155+
/* Overridden JsonNode methods, other
156+
/**********************************************************************
157+
*/
158+
153159
@Override
154160
public final void serialize(JsonGenerator g, SerializationContext provider)
155161
throws JacksonException

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

+26-22
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package tools.jackson.databind.node;
22

33
import java.util.Objects;
4+
import java.util.Optional;
45

56
import tools.jackson.core.*;
67
import tools.jackson.core.io.NumberInput;
78
import tools.jackson.core.util.ByteArrayBuilder;
89
import tools.jackson.databind.SerializationContext;
9-
import tools.jackson.databind.exc.InvalidFormatException;
1010

1111
/**
1212
* Value node that contains a String value.
@@ -64,11 +64,27 @@ protected String _valueDesc() {
6464
@Override
6565
public StringNode deepCopy() { return this; }
6666

67+
/*
68+
/**********************************************************************
69+
/* Overridden JsonNode methods, scalar access
70+
/**********************************************************************
71+
*/
72+
6773
@Override
6874
public String stringValue() {
6975
return _value;
7076
}
7177

78+
@Override
79+
public String stringValue(String defaultValue) {
80+
return _value;
81+
}
82+
83+
@Override
84+
public Optional<String> stringValueOpt() {
85+
return Optional.of(_value);
86+
}
87+
7288
/**
7389
* Method for accessing content String assuming they were
7490
* base64 encoded; if so, content is decoded and resulting binary
@@ -90,12 +106,8 @@ public byte[] getBinaryValue(Base64Variant b64variant) throws JacksonException
90106
try {
91107
b64variant.decode(str, builder);
92108
} catch (IllegalArgumentException e) {
93-
throw InvalidFormatException.from(
94-
null, /* Alas, no processor to pass */
95-
String.format(
96-
"Cannot access contents of `StringNode` as binary due to broken Base64 encoding: %s",
97-
e.getMessage()),
98-
str, byte[].class);
109+
return _reportCoercionFail("binaryValue()", byte[].class,
110+
"value type not binary and Base64-decoding failed with: "+e.getMessage());
99111
}
100112
return builder.toByteArray();
101113
}
@@ -105,34 +117,26 @@ public byte[] binaryValue() throws JacksonException {
105117
return getBinaryValue(Base64Variants.getDefaultVariant());
106118
}
107119

108-
/*
109-
/**********************************************************************
110-
/* General type coercions
111-
/**********************************************************************
112-
*/
113-
114120
@Override
115121
public String asString() {
116122
return _value;
117123
}
118124

119125
@Override
120126
public String asString(String defaultValue) {
121-
return (_value == null) ? defaultValue : _value;
127+
return _value;
122128
}
123129

124130
// note: neither fast nor elegant, but these work for now:
125131

126132
@Override
127133
public boolean asBoolean(boolean defaultValue) {
128-
if (_value != null) {
129-
String v = _value.trim();
130-
if ("true".equals(v)) {
131-
return true;
132-
}
133-
if ("false".equals(v)) {
134-
return false;
135-
}
134+
String v = _value.trim();
135+
if ("true".equals(v)) {
136+
return true;
137+
}
138+
if ("false".equals(v)) {
139+
return false;
136140
}
137141
return defaultValue;
138142
}

0 commit comments

Comments
 (0)