|
5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
6 | 6 | <title>java.util.json Backport for JDK 21+</title> |
7 | 7 | <style> |
| 8 | + :root { |
| 9 | + --bg: #f6f1e8; |
| 10 | + --paper: #fffaf2; |
| 11 | + --ink: #1f2933; |
| 12 | + --muted: #52616b; |
| 13 | + --accent: #0f766e; |
| 14 | + --accent-2: #b45309; |
| 15 | + --border: rgba(31, 41, 51, 0.12); |
| 16 | + --shadow: 0 18px 60px rgba(16, 24, 40, 0.10); |
| 17 | + } |
| 18 | + |
| 19 | + * { box-sizing: border-box; } |
| 20 | + |
8 | 21 | body { |
9 | | - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; |
10 | | - line-height: 1.6; |
11 | | - color: #333; |
12 | | - max-width: 900px; |
13 | | - margin: 0 auto; |
14 | | - padding: 20px; |
15 | | - background-color: #f5f5f5; |
| 22 | + margin: 0; |
| 23 | + padding: 24px; |
| 24 | + color: var(--ink); |
| 25 | + line-height: 1.65; |
| 26 | + font-family: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", serif; |
| 27 | + background: |
| 28 | + radial-gradient(1200px 600px at 18% 12%, rgba(15, 118, 110, 0.10), transparent 55%), |
| 29 | + radial-gradient(900px 500px at 85% 18%, rgba(180, 83, 9, 0.10), transparent 60%), |
| 30 | + linear-gradient(180deg, var(--bg), #f9f6ef); |
16 | 31 | } |
| 32 | + |
17 | 33 | .container { |
18 | | - background-color: white; |
19 | | - padding: 30px; |
20 | | - border-radius: 8px; |
21 | | - box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| 34 | + max-width: 980px; |
| 35 | + margin: 0 auto; |
| 36 | + padding: 28px; |
| 37 | + border-radius: 16px; |
| 38 | + background: var(--paper); |
| 39 | + border: 1px solid var(--border); |
| 40 | + box-shadow: var(--shadow); |
22 | 41 | } |
23 | | - h1, h2, h3 { |
24 | | - color: #2c3e50; |
| 42 | + |
| 43 | + header { |
| 44 | + padding: 6px 0 18px; |
| 45 | + border-bottom: 1px solid var(--border); |
| 46 | + margin-bottom: 22px; |
25 | 47 | } |
| 48 | + |
26 | 49 | h1 { |
27 | | - border-bottom: 3px solid #3498db; |
28 | | - padding-bottom: 10px; |
| 50 | + margin: 0 0 6px; |
| 51 | + font-size: clamp(28px, 3.3vw, 38px); |
| 52 | + letter-spacing: -0.02em; |
| 53 | + } |
| 54 | + |
| 55 | + h2 { |
| 56 | + margin: 22px 0 10px; |
| 57 | + font-size: 20px; |
| 58 | + letter-spacing: -0.01em; |
| 59 | + } |
| 60 | + |
| 61 | + h3 { |
| 62 | + margin: 14px 0 8px; |
| 63 | + font-size: 16px; |
| 64 | + } |
| 65 | + |
| 66 | + p, li { |
| 67 | + color: var(--ink); |
| 68 | + } |
| 69 | + |
| 70 | + .subhead { |
| 71 | + margin: 0; |
| 72 | + color: var(--muted); |
| 73 | + max-width: 70ch; |
| 74 | + } |
| 75 | + |
| 76 | + .callout { |
| 77 | + margin: 14px 0 0; |
| 78 | + padding: 12px 14px; |
| 79 | + border-radius: 12px; |
| 80 | + border: 1px solid rgba(15, 118, 110, 0.25); |
| 81 | + background: rgba(15, 118, 110, 0.06); |
| 82 | + } |
| 83 | + |
| 84 | + .grid { |
| 85 | + display: grid; |
| 86 | + grid-template-columns: 1fr; |
| 87 | + gap: 14px; |
| 88 | + } |
| 89 | + |
| 90 | + @media (min-width: 900px) { |
| 91 | + .grid { |
| 92 | + grid-template-columns: 1.1fr 0.9fr; |
| 93 | + gap: 18px; |
| 94 | + } |
29 | 95 | } |
| 96 | + |
30 | 97 | pre { |
31 | | - background-color: #f4f4f4; |
32 | | - border: 1px solid #ddd; |
33 | | - border-radius: 4px; |
34 | | - padding: 15px; |
| 98 | + margin: 10px 0 0; |
| 99 | + padding: 14px; |
35 | 100 | overflow-x: auto; |
| 101 | + border-radius: 12px; |
| 102 | + border: 1px solid var(--border); |
| 103 | + background: rgba(31, 41, 51, 0.04); |
36 | 104 | } |
| 105 | + |
37 | 106 | code { |
38 | | - background-color: #f4f4f4; |
39 | | - padding: 2px 4px; |
40 | | - border-radius: 3px; |
41 | | - font-family: 'Consolas', 'Monaco', 'Courier New', monospace; |
| 107 | + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; |
| 108 | + font-size: 0.95em; |
| 109 | + } |
| 110 | + |
| 111 | + pre code { |
| 112 | + background: transparent; |
42 | 113 | } |
43 | | - .highlight { |
44 | | - background-color: #fff3cd; |
45 | | - border-left: 4px solid #ffc107; |
46 | | - padding: 10px; |
47 | | - margin: 15px 0; |
| 114 | + |
| 115 | + .links { |
| 116 | + display: flex; |
| 117 | + flex-wrap: wrap; |
| 118 | + gap: 10px; |
| 119 | + margin-top: 12px; |
48 | 120 | } |
| 121 | + |
49 | 122 | .button { |
50 | 123 | display: inline-block; |
51 | | - padding: 10px 20px; |
52 | | - background-color: #3498db; |
53 | | - color: white; |
| 124 | + padding: 10px 14px; |
| 125 | + border-radius: 999px; |
54 | 126 | text-decoration: none; |
55 | | - border-radius: 5px; |
56 | | - margin-right: 10px; |
57 | | - margin-top: 10px; |
| 127 | + font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; |
| 128 | + font-weight: 650; |
| 129 | + border: 1px solid rgba(15, 118, 110, 0.35); |
| 130 | + background: rgba(15, 118, 110, 0.10); |
| 131 | + color: var(--ink); |
58 | 132 | } |
| 133 | + |
59 | 134 | .button:hover { |
60 | | - background-color: #2980b9; |
| 135 | + background: rgba(15, 118, 110, 0.16); |
61 | 136 | } |
| 137 | + |
62 | 138 | .button.secondary { |
63 | | - background-color: #95a5a6; |
| 139 | + border-color: rgba(180, 83, 9, 0.35); |
| 140 | + background: rgba(180, 83, 9, 0.10); |
64 | 141 | } |
| 142 | + |
65 | 143 | .button.secondary:hover { |
66 | | - background-color: #7f8c8d; |
| 144 | + background: rgba(180, 83, 9, 0.16); |
| 145 | + } |
| 146 | + |
| 147 | + .meta { |
| 148 | + color: var(--muted); |
| 149 | + font-size: 14px; |
67 | 150 | } |
68 | 151 | </style> |
69 | 152 | </head> |
70 | 153 | <body> |
71 | 154 | <div class="container"> |
72 | | - <h1>java.util.json Backport for JDK 21+</h1> |
| 155 | + <header> |
| 156 | + <h1>java.util.json Backport for JDK 21+</h1> |
| 157 | + <p class="subhead">A backport of the experimental <code>java.util.json</code> API from the OpenJDK jdk-sandbox “json” branch, plus an incubating JSON Type Definition (JTD) validator (RFC 8927).</p> |
| 158 | + <div class="callout"> |
| 159 | + <strong>Early access / unstable:</strong> APIs and behaviour may change as upstream evolves. |
| 160 | + </div> |
| 161 | + <div class="links"> |
| 162 | + <a href="https://central.sonatype.com/artifact/io.github.simbo1905.json/java.util.json" class="button">Maven Central</a> |
| 163 | + <a href="https://github.com/simbo1905/java.util.json.Java21" class="button">GitHub</a> |
| 164 | + <a href="Towards%20a%20JSON%20API%20for%20the%20JDK.pdf" class="button secondary">Design Paper (PDF)</a> |
| 165 | + <a href="https://github.com/openjdk/jdk-sandbox/tree/json" class="button secondary">OpenJDK jdk-sandbox</a> |
| 166 | + </div> |
| 167 | + </header> |
73 | 168 |
|
74 | | - <div class="highlight"> |
75 | | - <strong>Early Access:</strong> This is an unofficial backport of the experimental <code>java.util.json</code> API from OpenJDK sandbox, |
76 | | - enabling developers to use future JSON API patterns today on JDK 21+. |
| 169 | + <div class="grid"> |
| 170 | + <section> |
| 171 | + <h2>Quick Start</h2> |
| 172 | + |
| 173 | + <h3>Maven</h3> |
| 174 | + <pre><code><dependency> |
| 175 | + <groupId>io.github.simbo1905.json</groupId> |
| 176 | + <artifactId>java.util.json</artifactId> |
| 177 | + <version>2026.01.26</version> |
| 178 | +</dependency> |
| 179 | +</code></pre> |
| 180 | + |
| 181 | + <h3>Parse and access</h3> |
| 182 | + <pre><code>import jdk.sandbox.java.util.json.*; |
| 183 | + |
| 184 | +JsonObject obj = (JsonObject) Json.parse("{\"name\":\"Alice\",\"age\":30}"); |
| 185 | + |
| 186 | +String name = obj.get("name").string(); |
| 187 | +long age = obj.get("age").toLong();</code></pre> |
| 188 | + </section> |
| 189 | + |
| 190 | + <section> |
| 191 | + <h2>What’s Included</h2> |
| 192 | + <ul> |
| 193 | + <li><strong>Immutable JSON values:</strong> <code>JsonObject</code>, <code>JsonArray</code>, <code>JsonString</code>, <code>JsonNumber</code>, <code>JsonBoolean</code>, <code>JsonNull</code></li> |
| 194 | + <li><strong>Typed factories:</strong> build JSON with <code>JsonObject.of</code>, <code>JsonArray.of</code>, <code>JsonString.of</code>, <code>JsonNumber.of</code>, <code>JsonBoolean.of</code></li> |
| 195 | + <li><strong>Type-safe accessors:</strong> <code>obj.get("x").string()</code>, <code>value.toLong()</code>, <code>value.toDouble()</code>, <code>value.bool()</code></li> |
| 196 | + <li><strong>JTD validator (RFC 8927):</strong> module <code>json-java21-jtd</code> for real-world JSON-heavy logic</li> |
| 197 | + </ul> |
| 198 | + </section> |
77 | 199 | </div> |
78 | 200 |
|
79 | | - <h2>Quick Start</h2> |
80 | | - |
81 | | - <h3>Maven Dependency</h3> |
82 | | - <pre><code> |
83 | | - <dependency> |
84 | | - <groupId>io.github.simbo1905.json</groupId> |
85 | | - <artifactId>java.util.json</artifactId> |
86 | | - <version>0.1.8</version> |
87 | | - </dependency> |
88 | | - </code></pre> |
89 | | - |
90 | | - <h3>Simple Example</h3> |
| 201 | + <h2>Record Mapping (explicit, typed)</h2> |
91 | 202 | <pre><code>import jdk.sandbox.java.util.json.*; |
| 203 | +import java.util.*; |
92 | 204 |
|
93 | | -// Parse JSON |
94 | | -JsonValue value = Json.parse("{\"name\":\"Alice\",\"age\":30}"); |
95 | | -JsonObject obj = (JsonObject) value; |
96 | | - |
97 | | -// Access values |
98 | | -String name = ((JsonString) obj.members().get("name")).value(); |
99 | | -int age = ((JsonNumber) obj.members().get("age")).toNumber().intValue();</code></pre> |
100 | | - |
101 | | - <h2>Key Features</h2> |
102 | | - <ul> |
103 | | - <li><strong>Future-Compatible API:</strong> Write code today that will work with tomorrow's official <code>java.util.json</code></li> |
104 | | - <li><strong>Sealed Type Hierarchy:</strong> Type-safe JSON values using Java's sealed classes</li> |
105 | | - <li><strong>Record Integration:</strong> Seamless mapping between Java records and JSON</li> |
106 | | - <li><strong>Pattern Matching:</strong> Leverage modern Java features for elegant JSON handling</li> |
107 | | - <li><strong>Immutable Values:</strong> Thread-safe by design</li> |
108 | | - </ul> |
109 | | - |
110 | | - <h2>Record Mapping Example</h2> |
111 | | - <pre><code>// Domain model |
112 | 205 | record User(String name, String email, boolean active) {} |
113 | 206 | record Team(String teamName, List<User> members) {} |
114 | 207 |
|
115 | | -// Convert to JSON |
116 | 208 | Team team = new Team("Engineering", List.of( |
117 | 209 | new User("Alice", "alice@example.com", true), |
118 | 210 | new User("Bob", "bob@example.com", false) |
119 | 211 | )); |
120 | 212 |
|
121 | | -JsonValue teamJson = Json.fromUntyped(Map.of( |
122 | | - "teamName", team.teamName(), |
123 | | - "members", team.members().stream() |
124 | | - .map(u -> Map.of( |
125 | | - "name", u.name(), |
126 | | - "email", u.email(), |
127 | | - "active", u.active() |
128 | | - )) |
129 | | - .toList() |
| 213 | +JsonValue teamJson = JsonObject.of(Map.of( |
| 214 | + "teamName", JsonString.of(team.teamName()), |
| 215 | + "members", JsonArray.of(team.members().stream() |
| 216 | + .map(u -> JsonObject.of(Map.of( |
| 217 | + "name", JsonString.of(u.name()), |
| 218 | + "email", JsonString.of(u.email()), |
| 219 | + "active", JsonBoolean.of(u.active()) |
| 220 | + ))) |
| 221 | + .toList()) |
130 | 222 | ));</code></pre> |
131 | 223 |
|
132 | | - <h2>Resources</h2> |
133 | | - <a href="https://github.com/simbo1905/java.util.json.Java21" class="button">View on GitHub</a> |
134 | | - <a href="Towards%20a%20JSON%20API%20for%20the%20JDK.pdf" class="button secondary">Original Proposal</a> |
135 | | - <a href="https://github.com/openjdk/jdk-sandbox/tree/json" class="button secondary">OpenJDK Sandbox</a> |
| 224 | + <h2>Run the README Examples</h2> |
| 225 | + <pre><code>mvn package |
| 226 | +java -cp ./json-java21/target/java.util.json-*.jar:./json-java21/target/test-classes \ |
| 227 | + jdk.sandbox.java.util.json.examples.ReadmeExamples</code></pre> |
| 228 | + <p class="meta">Replace <code>*</code> with the actual version number from the JAR filename.</p> |
| 229 | + |
| 230 | + <h2>JSON Test Suite Compatibility</h2> |
| 231 | + <pre><code># Run human-readable report |
| 232 | +mvn exec:java -pl json-compatibility-suite |
| 233 | + |
| 234 | +# Run JSON output (dogfoods the API) |
| 235 | +mvn exec:java -pl json-compatibility-suite -Dexec.args="--json"</code></pre> |
| 236 | + |
| 237 | + <h2>JSON Type Definition (JTD) Validator</h2> |
| 238 | + <p class="meta">This repo includes an incubating JTD validator (RFC 8927) in module <code>json-java21-jtd</code>. Per RFC 8927, the empty schema <code>{}</code> accepts all JSON instances.</p> |
| 239 | + <pre><code>import json.java21.jtd.Jtd; |
| 240 | +import jdk.sandbox.java.util.json.*; |
| 241 | + |
| 242 | +JsonValue schema = Json.parse(""" |
| 243 | + { |
| 244 | + "properties": { |
| 245 | + "name": {"type": "string"}, |
| 246 | + "age": {"type": "int32"} |
| 247 | + } |
| 248 | + } |
| 249 | +"""); |
| 250 | + |
| 251 | +JsonValue data = Json.parse("{\"name\":\"Alice\",\"age\":30}"); |
| 252 | + |
| 253 | +Jtd.Result result = new Jtd().validate(schema, data); |
| 254 | +// result.isValid() == true</code></pre> |
136 | 255 |
|
137 | 256 | <h2>Status</h2> |
138 | | - <p> |
139 | | - This code (as of 2025-09-04) is derived from the OpenJDK jdk-sandbox repository “json” |
140 | | - branch at commit |
141 | | - <a href="https://github.com/openjdk/jdk-sandbox/commit/a8e7de8b49e4e4178eb53c94ead2fa2846c30635">Produce path/col during path building</a>. |
142 | | - The API may evolve as the official specification develops. |
143 | | - </p> |
| 257 | + <p class="meta">This code (as of 2026-01-25) is derived from the OpenJDK jdk-sandbox repository “json” branch. The API may evolve as the upstream design develops.</p> |
144 | 258 |
|
145 | 259 | <h2>License</h2> |
146 | | - <p>Licensed under the GNU General Public License version 2 with Classpath exception, |
147 | | - same as the OpenJDK project.</p> |
| 260 | + <p class="meta">GNU General Public License version 2 with Classpath exception (same as OpenJDK).</p> |
148 | 261 | </div> |
149 | 262 | </body> |
150 | 263 | </html> |
0 commit comments