Skip to content

Commit 3f62ed2

Browse files
cursoragentsimbo1905
andcommitted
Issue #119 Add counter examples and native mapping guidance
Co-authored-by: simbo1905 <simbo1905@60hertz.com>
1 parent 1196242 commit 3f62ed2

File tree

1 file changed

+71
-0
lines changed

1 file changed

+71
-0
lines changed

DESIGN_CHOICES.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ During the last upstream sync, those entry points were removed. **That is consis
1313

1414
Put differently: the design is “JSON numbers are text first”, not “JSON numbers are a Java numeric tower”.
1515

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+
1634
## Why upstream prefers `String` (and why BigDecimal constructors are a footgun)
1735

1836
### 1) JSON numbers are arbitrary precision *text*
@@ -58,6 +76,28 @@ var n = (JsonNumber) Json.parse("3.141592653589793238462643383279");
5876
var bd = new BigDecimal(n.toString()); // exact
5977
```
6078

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)
95+
96+
```java
97+
var n = (JsonNumber) Json.parse("1e309");
98+
n.toDouble(); // throws JsonAssertionException (outside finite double range)
99+
```
100+
61101
### Parse → BigInteger (lossless, when integral)
62102

63103
```java
@@ -93,6 +133,37 @@ assert !a.toString().equals(b.toString()); // lexical difference preserved
93133

94134
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.
95135

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+
static Object toNative(JsonValue v) {
147+
return switch (v) {
148+
case JsonNull ignored -> null;
149+
case JsonBoolean b -> b.bool();
150+
case JsonString s -> s.string();
151+
case JsonNumber n -> {
152+
try {
153+
yield n.toLong();
154+
} catch (JsonAssertionException ignored) {
155+
yield new BigDecimal(n.toString());
156+
}
157+
}
158+
case JsonArray a -> a.elements().stream().map(Design::toNative).toList();
159+
case JsonObject 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.
166+
96167
## Runnable examples
97168

98169
This document’s examples are mirrored in code:

0 commit comments

Comments
 (0)