You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: DESIGN_CHOICES.md
+71Lines changed: 71 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,6 +13,24 @@ During the last upstream sync, those entry points were removed. **That is consis
13
13
14
14
Put differently: the design is “JSON numbers are text first”, not “JSON numbers are a Java numeric tower”.
15
15
16
+
## `JsonNumber` is not a primitive (and that’s the point)
17
+
18
+
The core abstraction here is `JsonValue`, a **sealed interface** with one subtype per JSON kind:
19
+
20
+
-`JsonString`
21
+
-`JsonNumber`
22
+
-`JsonObject`
23
+
-`JsonArray`
24
+
-`JsonBoolean`
25
+
-`JsonNull`
26
+
27
+
So `JsonNumber` is not intended to *replace* Java numeric primitives; it’s the JSON-layer representation of “a number token in a JSON document”.
28
+
29
+
The deliberate split is:
30
+
31
+
-**JSON layer**: preserve what was written (especially for numbers), keep round-tripping sane, avoid choosing a single “native numeric type” too early.
32
+
-**Application layer**: *you* decide what “native” means (long? double? BigDecimal? BigInteger? domain-specific types?), and you do that conversion explicitly.
33
+
16
34
## Why upstream prefers `String` (and why BigDecimal constructors are a footgun)
17
35
18
36
### 1) JSON numbers are arbitrary precision *text*
@@ -58,6 +76,28 @@ var n = (JsonNumber) Json.parse("3.141592653589793238462643383279");
58
76
var bd =newBigDecimal(n.toString()); // exact
59
77
```
60
78
79
+
### Counter-example: converting to `double` can lose information
80
+
81
+
```java
82
+
var n = (JsonNumber) Json.parse("3.141592653589793238462643383279");
83
+
double d = n.toDouble(); // finite, but lossy
84
+
// BigDecimal.valueOf(d) is NOT equal to the original high-precision value
85
+
```
86
+
87
+
### Counter-example: converting to `long` can throw (even for numbers)
88
+
89
+
```java
90
+
var n = (JsonNumber) Json.parse("5.5");
91
+
n.toLong(); // throws JsonAssertionException (not an integral value)
92
+
```
93
+
94
+
### Counter-example: converting to `double` can throw (range overflow)
If your application needs *numeric* equality or canonicalization, perform it explicitly with `BigDecimal` (or your own policy), rather than relying on the JSON value object to do it implicitly.
95
135
136
+
## Ergonomics: mapping `JsonValue` to native Java types (pattern matching)
137
+
138
+
If you want the “old style” `Map` / `List` / primitives view, you can build it explicitly using a `switch` over the sealed `JsonValue` hierarchy.
139
+
140
+
One pragmatic policy for numbers is:
141
+
142
+
- try `toLong()` first (exact integer in range)
143
+
- otherwise fall back to `BigDecimal` from `toString()` (lossless)
144
+
145
+
```java
146
+
staticObject toNative(JsonValue v) {
147
+
returnswitch (v) {
148
+
caseJsonNull ignored ->null;
149
+
caseJsonBoolean b -> b.bool();
150
+
caseJsonString s -> s.string();
151
+
caseJsonNumber n -> {
152
+
try {
153
+
yield n.toLong();
154
+
} catch (JsonAssertionException ignored) {
155
+
yield newBigDecimal(n.toString());
156
+
}
157
+
}
158
+
caseJsonArray a -> a.elements().stream().map(Design::toNative).toList();
159
+
caseJsonObject o -> o.members().entrySet().stream()
160
+
.collect(Collectors.toMap(Map.Entry::getKey, e -> toNative(e.getValue())));
161
+
};
162
+
}
163
+
```
164
+
165
+
This gives you native ergonomics **without** forcing the core JSON API to guess which numeric type you wanted.
0 commit comments