Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
types: [opened, synchronize, reopened]

jobs:
build:
Expand Down
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,40 @@ The API provides immutable JSON value types:
Parsing is done via the `Json` class:
```java
JsonValue value = Json.parse(jsonString);
```
```

## Type Conversion Utilities

The `Json` class provides bidirectional conversion between `JsonValue` objects and standard Java types:

### Converting from Java Objects to JSON (`fromUntyped`)
```java
// Convert standard Java collections to JsonValue
Map<String, Object> data = Map.of(
"name", "John",
"age", 30,
"scores", List.of(85, 92, 78)
);
JsonValue json = Json.fromUntyped(data);
```

### Converting from JSON to Java Objects (`toUntyped`)
```java
// Convert JsonValue back to standard Java types
JsonValue parsed = Json.parse("{\"name\":\"John\",\"age\":30}");
Object data = Json.toUntyped(parsed);
// Returns a Map<String, Object> with standard Java types
```

The conversion mappings are:
- `JsonObject` ↔ `Map<String, Object>`
- `JsonArray` ↔ `List<Object>`
- `JsonString` ↔ `String`
- `JsonNumber` ↔ `Number` (Long, Double, BigInteger, or BigDecimal)
- `JsonBoolean` ↔ `Boolean`
- `JsonNull` ↔ `null`

This is useful for:
- Integrating with existing code that uses standard collections
- Serializing/deserializing to formats that expect Java types
- Working with frameworks that use reflection on standard types
4 changes: 2 additions & 2 deletions src/main/java/jdk/sandbox/demo/JsonDemo.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public static void main(String[] args) {
"age", JsonNumber.of(30)
));

System.out.println(jsonObject.toString());
System.out.println(jsonObject);

// Parse JSON string
String jsonStr = "{\"name\":\"Jane\",\"age\":25}";
var parsed = Json.parse(jsonStr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public final class JsonNumberImpl implements JsonNumber {
private final int startOffset;
private final int endOffset;
private final boolean isFp;
private final jdk.sandbox.StableValue<Number> theNumber = jdk.sandbox.StableValue.of();
private final jdk.sandbox.StableValue<String> numString = jdk.sandbox.StableValue.of();
private final jdk.sandbox.StableValue<BigDecimal> cachedBD = jdk.sandbox.StableValue.of();
private final StableValue<Number> theNumber = StableValue.of();
private final StableValue<String> numString = StableValue.of();
private final StableValue<BigDecimal> cachedBD = StableValue.of();

public JsonNumberImpl(Number num) {
// Called by factories. Input is Double, Long, BI, or BD.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public final class JsonParser {
// Access to the underlying JSON contents
private final char[] doc;
// Lazily initialized for member names with escape sequences
private final Supplier<StringBuilder> sb = jdk.sandbox.StableValue.supplier(this::initSb);
private final Supplier<StringBuilder> sb = StableValue.supplier(this::initSb);
// Current offset during parsing
private int offset;
// For exception message on failure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ public final class JsonStringImpl implements JsonString {
// It always conforms to JSON syntax. If created by parsing a JSON document,
// it matches the original text exactly. If created via the factory method,
// non-conformant characters are properly escaped.
private final jdk.sandbox.StableValue<String> jsonStr = jdk.sandbox.StableValue.of();
private final StableValue<String> jsonStr = StableValue.of();

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

// Called by JsonString.of() factory. The passed String represents the
// unescaped value.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,26 @@
package jdk.sandbox;
package jdk.sandbox.internal.util.json;

import java.util.function.Supplier;

/**
* Mimics JDK's StableValue using double-checked locking pattern
* for thread-safe lazy initialization.
*/
public class StableValue<T> {
class StableValue<T> {
private volatile T value;
private final Object lock = new Object();

private StableValue() {}

public static <T> StableValue<T> of() {
return new StableValue<>();
}

public T get() {
return value;
}

public void set(T value) {
if (this.value == null) {
synchronized (lock) {
if (this.value == null) {
this.value = value;
}
}
}
}

public T orElse(T defaultValue) {

public T orElse(T defaultValue) {
T result = value;
return result != null ? result : defaultValue;
}

public T orElseSet(Supplier<T> supplier) {
T result = value;
if (result == null) {
Expand All @@ -47,7 +33,7 @@ public T orElseSet(Supplier<T> supplier) {
}
return result;
}

public void setOrThrow(T newValue) {
if (value != null) {
throw new IllegalStateException("Value already set");
Expand All @@ -59,25 +45,25 @@ public void setOrThrow(T newValue) {
value = newValue;
}
}

public static <T> Supplier<T> supplier(Supplier<T> supplier) {
return new Supplier<T>() {
private volatile T cached;
private final Object supplierLock = new Object();

@Override
public T get() {
T result = cached;
return new Supplier<>() {
private volatile T cached;
private final Object supplierLock = new Object();

@Override
public T get() {
T result = cached;
if (result == null) {
synchronized (supplierLock) {
result = cached;
if (result == null) {
synchronized (supplierLock) {
result = cached;
if (result == null) {
cached = result = supplier.get();
}
}
cached = result = supplier.get();
}
return result;
}
}
return result;
}
};
}
}
4 changes: 2 additions & 2 deletions src/main/java/jdk/sandbox/java/util/json/Json.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
* <p>
* {@link #fromUntyped(Object)} and {@link #toUntyped(JsonValue)} provide a conversion
* between {@code JsonValue} and an untyped object.
*
* @spec https://datatracker.ietf.org/doc/html/rfc8259 RFC 8259: The JavaScript
* <p>
* {@code @spec} <a href="https://datatracker.ietf.org/doc/html/rfc8259">...</a> RFC 8259: The JavaScript
* Object Notation (JSON) Data Interchange Format
* @since 99
*/
Expand Down
Loading