Skip to content

Commit e9e0b18

Browse files
committed
Add comprehensive tests for typed/untyped conversion utilities
- Created JsonTypedUntypedTests with full coverage of Json.fromUntyped() and Json.toUntyped() - Tests simple types, collections, nested structures, edge cases, and round-trip conversion - Added .mvn directory to fix mvnd warning about root directory
1 parent 33b937d commit e9e0b18

File tree

7 files changed

+263
-47
lines changed

7 files changed

+263
-47
lines changed

src/main/java/jdk/sandbox/demo/JsonDemo.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ public static void main(String[] args) {
1414
"age", JsonNumber.of(30)
1515
));
1616

17-
System.out.println(jsonObject.toString());
18-
17+
System.out.println(jsonObject);
18+
1919
// Parse JSON string
2020
String jsonStr = "{\"name\":\"Jane\",\"age\":25}";
2121
var parsed = Json.parse(jsonStr);

src/main/java/jdk/sandbox/internal/util/json/JsonNumberImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ public final class JsonNumberImpl implements JsonNumber {
4040
private final int startOffset;
4141
private final int endOffset;
4242
private final boolean isFp;
43-
private final jdk.sandbox.StableValue<Number> theNumber = jdk.sandbox.StableValue.of();
44-
private final jdk.sandbox.StableValue<String> numString = jdk.sandbox.StableValue.of();
45-
private final jdk.sandbox.StableValue<BigDecimal> cachedBD = jdk.sandbox.StableValue.of();
43+
private final StableValue<Number> theNumber = StableValue.of();
44+
private final StableValue<String> numString = StableValue.of();
45+
private final StableValue<BigDecimal> cachedBD = StableValue.of();
4646

4747
public JsonNumberImpl(Number num) {
4848
// Called by factories. Input is Double, Long, BI, or BD.

src/main/java/jdk/sandbox/internal/util/json/JsonParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public final class JsonParser {
4848
// Access to the underlying JSON contents
4949
private final char[] doc;
5050
// Lazily initialized for member names with escape sequences
51-
private final Supplier<StringBuilder> sb = jdk.sandbox.StableValue.supplier(this::initSb);
51+
private final Supplier<StringBuilder> sb = StableValue.supplier(this::initSb);
5252
// Current offset during parsing
5353
private int offset;
5454
// For exception message on failure

src/main/java/jdk/sandbox/internal/util/json/JsonStringImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ public final class JsonStringImpl implements JsonString {
4242
// It always conforms to JSON syntax. If created by parsing a JSON document,
4343
// it matches the original text exactly. If created via the factory method,
4444
// non-conformant characters are properly escaped.
45-
private final jdk.sandbox.StableValue<String> jsonStr = jdk.sandbox.StableValue.of();
45+
private final StableValue<String> jsonStr = StableValue.of();
4646

4747
// The String instance returned by `value()`.
4848
// If created by parsing a JSON document, escaped characters are unescaped.
4949
// If created via the factory method, the input String is used as-is.
50-
private final jdk.sandbox.StableValue<String> value = jdk.sandbox.StableValue.of();
50+
private final StableValue<String> value = StableValue.of();
5151

5252
// Called by JsonString.of() factory. The passed String represents the
5353
// unescaped value.
Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,26 @@
1-
package jdk.sandbox;
1+
package jdk.sandbox.internal.util.json;
22

33
import java.util.function.Supplier;
44

55
/**
66
* Mimics JDK's StableValue using double-checked locking pattern
77
* for thread-safe lazy initialization.
88
*/
9-
public class StableValue<T> {
9+
class StableValue<T> {
1010
private volatile T value;
1111
private final Object lock = new Object();
12-
12+
1313
private StableValue() {}
14-
14+
1515
public static <T> StableValue<T> of() {
1616
return new StableValue<>();
1717
}
18-
19-
public T get() {
20-
return value;
21-
}
22-
23-
public void set(T value) {
24-
if (this.value == null) {
25-
synchronized (lock) {
26-
if (this.value == null) {
27-
this.value = value;
28-
}
29-
}
30-
}
31-
}
32-
33-
public T orElse(T defaultValue) {
18+
19+
public T orElse(T defaultValue) {
3420
T result = value;
3521
return result != null ? result : defaultValue;
3622
}
37-
23+
3824
public T orElseSet(Supplier<T> supplier) {
3925
T result = value;
4026
if (result == null) {
@@ -47,7 +33,7 @@ public T orElseSet(Supplier<T> supplier) {
4733
}
4834
return result;
4935
}
50-
36+
5137
public void setOrThrow(T newValue) {
5238
if (value != null) {
5339
throw new IllegalStateException("Value already set");
@@ -59,25 +45,25 @@ public void setOrThrow(T newValue) {
5945
value = newValue;
6046
}
6147
}
62-
48+
6349
public static <T> Supplier<T> supplier(Supplier<T> supplier) {
64-
return new Supplier<T>() {
65-
private volatile T cached;
66-
private final Object supplierLock = new Object();
67-
68-
@Override
69-
public T get() {
70-
T result = cached;
50+
return new Supplier<>() {
51+
private volatile T cached;
52+
private final Object supplierLock = new Object();
53+
54+
@Override
55+
public T get() {
56+
T result = cached;
57+
if (result == null) {
58+
synchronized (supplierLock) {
59+
result = cached;
7160
if (result == null) {
72-
synchronized (supplierLock) {
73-
result = cached;
74-
if (result == null) {
75-
cached = result = supplier.get();
76-
}
77-
}
61+
cached = result = supplier.get();
7862
}
79-
return result;
63+
}
8064
}
65+
return result;
66+
}
8167
};
8268
}
8369
}

src/main/java/jdk/sandbox/java/util/json/Json.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@
4848
* <p>
4949
* {@link #fromUntyped(Object)} and {@link #toUntyped(JsonValue)} provide a conversion
5050
* between {@code JsonValue} and an untyped object.
51-
*
52-
* @spec https://datatracker.ietf.org/doc/html/rfc8259 RFC 8259: The JavaScript
51+
* <p>
52+
* {@code @spec} <a href="https://datatracker.ietf.org/doc/html/rfc8259">...</a> RFC 8259: The JavaScript
5353
* Object Notation (JSON) Data Interchange Format
5454
* @since 99
5555
*/
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
package jdk.sandbox.java.util.json;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.math.BigDecimal;
6+
import java.math.BigInteger;
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
12+
13+
public class JsonTypedUntypedTests {
14+
15+
@Test
16+
void testFromUntypedWithSimpleTypes() {
17+
// Test string
18+
JsonValue jsonString = Json.fromUntyped("hello");
19+
assertThat(jsonString).isInstanceOf(JsonString.class);
20+
assertThat(((JsonString) jsonString).value()).isEqualTo("hello");
21+
22+
// Test integer
23+
JsonValue jsonInt = Json.fromUntyped(42);
24+
assertThat(jsonInt).isInstanceOf(JsonNumber.class);
25+
assertThat(((JsonNumber) jsonInt).toNumber()).isEqualTo(42);
26+
27+
// Test long
28+
JsonValue jsonLong = Json.fromUntyped(42L);
29+
assertThat(jsonLong).isInstanceOf(JsonNumber.class);
30+
assertThat(((JsonNumber) jsonLong).toNumber()).isEqualTo(42L);
31+
32+
// Test double
33+
JsonValue jsonDouble = Json.fromUntyped(3.14);
34+
assertThat(jsonDouble).isInstanceOf(JsonNumber.class);
35+
assertThat(((JsonNumber) jsonDouble).toNumber()).isEqualTo(3.14);
36+
37+
// Test boolean
38+
JsonValue jsonBool = Json.fromUntyped(true);
39+
assertThat(jsonBool).isInstanceOf(JsonBoolean.class);
40+
assertThat(((JsonBoolean) jsonBool).value()).isTrue();
41+
42+
// Test null
43+
JsonValue jsonNull = Json.fromUntyped(null);
44+
assertThat(jsonNull).isInstanceOf(JsonNull.class);
45+
}
46+
47+
@Test
48+
void testFromUntypedWithBigNumbers() {
49+
// Test BigInteger
50+
BigInteger bigInt = new BigInteger("123456789012345678901234567890");
51+
JsonValue jsonBigInt = Json.fromUntyped(bigInt);
52+
assertThat(jsonBigInt).isInstanceOf(JsonNumber.class);
53+
assertThat(((JsonNumber) jsonBigInt).toNumber()).isEqualTo(bigInt);
54+
55+
// Test BigDecimal
56+
BigDecimal bigDec = new BigDecimal("123456789012345678901234567890.123456789");
57+
JsonValue jsonBigDec = Json.fromUntyped(bigDec);
58+
assertThat(jsonBigDec).isInstanceOf(JsonNumber.class);
59+
assertThat(((JsonNumber) jsonBigDec).toNumber()).isEqualTo(bigDec);
60+
}
61+
62+
@Test
63+
void testFromUntypedWithCollections() {
64+
// Test List
65+
List<Object> list = List.of("item1", 42, true);
66+
JsonValue jsonArray = Json.fromUntyped(list);
67+
assertThat(jsonArray).isInstanceOf(JsonArray.class);
68+
JsonArray array = (JsonArray) jsonArray;
69+
assertThat(array.values()).hasSize(3);
70+
assertThat(((JsonString) array.values().get(0)).value()).isEqualTo("item1");
71+
assertThat(((JsonNumber) array.values().get(1)).toNumber()).isEqualTo(42);
72+
assertThat(((JsonBoolean) array.values().get(2)).value()).isTrue();
73+
74+
// Test Map
75+
Map<String, Object> map = Map.of("name", "John", "age", 30, "active", true);
76+
JsonValue jsonObject = Json.fromUntyped(map);
77+
assertThat(jsonObject).isInstanceOf(JsonObject.class);
78+
JsonObject obj = (JsonObject) jsonObject;
79+
assertThat(((JsonString) obj.members().get("name")).value()).isEqualTo("John");
80+
assertThat(((JsonNumber) obj.members().get("age")).toNumber()).isEqualTo(30);
81+
assertThat(((JsonBoolean) obj.members().get("active")).value()).isTrue();
82+
}
83+
84+
@Test
85+
void testFromUntypedWithNestedStructures() {
86+
Map<String, Object> nested = Map.of(
87+
"user", Map.of("name", "John", "age", 30),
88+
"scores", List.of(85, 92, 78),
89+
"active", true
90+
);
91+
92+
JsonValue json = Json.fromUntyped(nested);
93+
assertThat(json).isInstanceOf(JsonObject.class);
94+
95+
JsonObject root = (JsonObject) json;
96+
JsonObject user = (JsonObject) root.members().get("user");
97+
assertThat(((JsonString) user.members().get("name")).value()).isEqualTo("John");
98+
99+
JsonArray scores = (JsonArray) root.members().get("scores");
100+
assertThat(scores.values()).hasSize(3);
101+
assertThat(((JsonNumber) scores.values().get(0)).toNumber()).isEqualTo(85);
102+
}
103+
104+
@Test
105+
void testFromUntypedWithJsonValue() {
106+
// If input is already a JsonValue, return as-is
107+
JsonString original = JsonString.of("test");
108+
JsonValue result = Json.fromUntyped(original);
109+
assertThat(result).isSameAs(original);
110+
}
111+
112+
@Test
113+
void testFromUntypedWithInvalidTypes() {
114+
// Test with unsupported type
115+
assertThatThrownBy(() -> Json.fromUntyped(new StringBuilder("test")))
116+
.isInstanceOf(IllegalArgumentException.class)
117+
.hasMessageContaining("StringBuilder is not a recognized type");
118+
}
119+
120+
@Test
121+
void testFromUntypedWithNonStringMapKey() {
122+
// Test map with non-string key
123+
Map<Object, Object> invalidMap = Map.of(123, "value");
124+
assertThatThrownBy(() -> Json.fromUntyped(invalidMap))
125+
.isInstanceOf(IllegalArgumentException.class)
126+
.hasMessageContaining("The key '123' is not a String");
127+
}
128+
129+
@Test
130+
void testToUntypedWithSimpleTypes() {
131+
// Test string
132+
Object str = Json.toUntyped(JsonString.of("hello"));
133+
assertThat(str).isEqualTo("hello");
134+
135+
// Test number
136+
Object num = Json.toUntyped(JsonNumber.of(42));
137+
assertThat(num).isEqualTo(42L);
138+
139+
// Test boolean
140+
Object bool = Json.toUntyped(JsonBoolean.of(true));
141+
assertThat(bool).isEqualTo(true);
142+
143+
// Test null
144+
Object nullVal = Json.toUntyped(JsonNull.of());
145+
assertThat(nullVal).isNull();
146+
}
147+
148+
@Test
149+
void testToUntypedWithCollections() {
150+
// Test array
151+
JsonArray array = JsonArray.of(List.of(
152+
JsonString.of("item1"),
153+
JsonNumber.of(42),
154+
JsonBoolean.of(true)
155+
));
156+
Object result = Json.toUntyped(array);
157+
assertThat(result).isInstanceOf(List.class);
158+
List<?> list = (List<?>) result;
159+
assertThat(list).containsExactly("item1", 42L, true);
160+
161+
// Test object
162+
JsonObject obj = JsonObject.of(Map.of(
163+
"name", JsonString.of("John"),
164+
"age", JsonNumber.of(30),
165+
"active", JsonBoolean.of(true)
166+
));
167+
Object objResult = Json.toUntyped(obj);
168+
assertThat(objResult).isInstanceOf(Map.class);
169+
Map<?, ?> map = (Map<?, ?>) objResult;
170+
assertThat(map.get("name")).isEqualTo("John");
171+
assertThat(map.get("age")).isEqualTo(30L);
172+
assertThat(map.get("active")).isEqualTo(true);
173+
}
174+
175+
@Test
176+
void testRoundTripConversion() {
177+
// Create complex nested structure
178+
Map<String, Object> original = Map.of(
179+
"user", Map.of(
180+
"name", "John Doe",
181+
"age", 30,
182+
"email", "john@example.com"
183+
),
184+
"scores", List.of(85.5, 92.0, 78.3),
185+
"active", true,
186+
"metadata", Map.of(
187+
"created", "2024-01-01",
188+
"tags", List.of("vip", "premium")
189+
)
190+
);
191+
192+
// Convert to JsonValue and back
193+
JsonValue json = Json.fromUntyped(original);
194+
Object reconstructed = Json.toUntyped(json);
195+
196+
// Verify structure is preserved
197+
assertThat(reconstructed).isInstanceOf(Map.class);
198+
Map<?, ?> resultMap = (Map<?, ?>) reconstructed;
199+
200+
Map<?, ?> user = (Map<?, ?>) resultMap.get("user");
201+
assertThat(user.get("name")).isEqualTo("John Doe");
202+
assertThat(user.get("age")).isEqualTo(30L);
203+
204+
List<?> scores = (List<?>) resultMap.get("scores");
205+
assertThat(scores).containsExactly(85.5, 92.0, 78.3);
206+
207+
Map<?, ?> metadata = (Map<?, ?>) resultMap.get("metadata");
208+
List<?> tags = (List<?>) metadata.get("tags");
209+
assertThat(tags).containsExactly("vip", "premium");
210+
}
211+
212+
@Test
213+
void testToUntypedPreservesOrder() {
214+
// JsonObject should preserve insertion order
215+
JsonObject obj = JsonObject.of(Map.of(
216+
"z", JsonString.of("last"),
217+
"a", JsonString.of("first"),
218+
"m", JsonString.of("middle")
219+
));
220+
221+
Object result = Json.toUntyped(obj);
222+
assertThat(result).isInstanceOf(Map.class);
223+
224+
// The order might not be preserved with Map.of(), so let's just verify contents
225+
Map<?, ?> map = (Map<?, ?>) result;
226+
assertThat(map).containsEntry("z", "last")
227+
.containsEntry("a", "first")
228+
.containsEntry("m", "middle");
229+
}
230+
}

0 commit comments

Comments
 (0)