Skip to content

Commit 8bfdbb4

Browse files
authored
Guarantee that JsonElement.toString() produces JSON (#2659)
* Extend `JsonElement` documentation * Add space to JsonParser code example * Guarantee that `JsonElement.toString()` produces JSON * Refer to `JsonElement` documentation from subclasses
1 parent 9008b09 commit 8bfdbb4

File tree

9 files changed

+172
-4
lines changed

9 files changed

+172
-4
lines changed

gson/src/main/java/com/google/gson/JsonArray.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
* <p>{@code JsonArray} only implements the {@link Iterable} interface but not the {@link List}
3434
* interface. A {@code List} view of it can be obtained with {@link #asList()}.
3535
*
36+
* <p>See the {@link JsonElement} documentation for details on how to convert {@code JsonArray} and
37+
* generally any {@code JsonElement} from and to JSON.
38+
*
3639
* @author Inderjeet Singh
3740
* @author Joel Leitch
3841
*/

gson/src/main/java/com/google/gson/JsonElement.java

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.gson.internal.Streams;
2121
import com.google.gson.stream.JsonWriter;
2222
import java.io.IOException;
23+
import java.io.Reader;
2324
import java.io.StringWriter;
2425
import java.math.BigDecimal;
2526
import java.math.BigInteger;
@@ -28,6 +29,69 @@
2829
* A class representing an element of JSON. It could either be a {@link JsonObject}, a {@link
2930
* JsonArray}, a {@link JsonPrimitive} or a {@link JsonNull}.
3031
*
32+
* <p>This class provides multiple {@code getAs} methods which allow
33+
*
34+
* <ul>
35+
* <li>obtaining the represented primitive value, for example {@link #getAsString()}
36+
* <li>casting to the {@code JsonElement} subclasses in a convenient way, for example {@link
37+
* #getAsJsonObject()}
38+
* </ul>
39+
*
40+
* <h2>Converting {@code JsonElement} from / to JSON</h2>
41+
*
42+
* There are two ways to parse JSON data as a {@link JsonElement}:
43+
*
44+
* <ul>
45+
* <li>{@link JsonParser}, for example:
46+
* <pre>
47+
* JsonObject jsonObject = JsonParser.parseString("{}").getAsJsonObject();
48+
* </pre>
49+
* <li>{@link Gson#fromJson(Reader, Class) Gson.fromJson(..., JsonElement.class)}<br>
50+
* It is possible to directly specify a {@code JsonElement} subclass, for example:
51+
* <pre>
52+
* JsonObject jsonObject = gson.fromJson("{}", JsonObject.class);
53+
* </pre>
54+
* </ul>
55+
*
56+
* To convert a {@code JsonElement} to JSON either {@link #toString() JsonElement.toString()} or the
57+
* method {@link Gson#toJson(JsonElement)} and its overloads can be used.
58+
*
59+
* <p>It is also possible to obtain the {@link TypeAdapter} for {@code JsonElement} from a {@link
60+
* Gson} instance and then use it for conversion from and to JSON:
61+
*
62+
* <pre>{@code
63+
* TypeAdapter<JsonElement> adapter = gson.getAdapter(JsonElement.class);
64+
*
65+
* JsonElement value = adapter.fromJson("{}");
66+
* String json = adapter.toJson(value);
67+
* }</pre>
68+
*
69+
* <h2>{@code JsonElement} as JSON data</h2>
70+
*
71+
* {@code JsonElement} can also be treated as JSON data, allowing to deserialize from a {@code
72+
* JsonElement} and serializing to a {@code JsonElement}. The {@link Gson} class offers these
73+
* methods for this:
74+
*
75+
* <ul>
76+
* <li>{@link Gson#fromJson(JsonElement, Class) Gson.fromJson(JsonElement, ...)}, for example:
77+
* <pre>
78+
* JsonObject jsonObject = ...;
79+
* MyClass myObj = gson.fromJson(jsonObject, MyClass.class);
80+
* </pre>
81+
* <li>{@link Gson#toJsonTree(Object)}, for example:
82+
* <pre>
83+
* MyClass myObj = ...;
84+
* JsonElement json = gson.toJsonTree(myObj);
85+
* </pre>
86+
* </ul>
87+
*
88+
* The {@link TypeAdapter} class provides corresponding methods as well:
89+
*
90+
* <ul>
91+
* <li>{@link TypeAdapter#fromJsonTree(JsonElement)}
92+
* <li>{@link TypeAdapter#toJsonTree(Object)}
93+
* </ul>
94+
*
3195
* @author Inderjeet Singh
3296
* @author Joel Leitch
3397
*/
@@ -320,7 +384,41 @@ public short getAsShort() {
320384
throw new UnsupportedOperationException(getClass().getSimpleName());
321385
}
322386

323-
/** Returns a String representation of this element. */
387+
/**
388+
* Converts this element to a JSON string.
389+
*
390+
* <p>For example:
391+
*
392+
* <pre>
393+
* JsonObject object = new JsonObject();
394+
* object.add("a", JsonNull.INSTANCE);
395+
* JsonArray array = new JsonArray();
396+
* array.add(1);
397+
* object.add("b", array);
398+
*
399+
* String json = object.toString();
400+
* // json: {"a":null,"b":[1]}
401+
* </pre>
402+
*
403+
* If this element or any nested elements contain {@link Double#NaN NaN} or {@link
404+
* Double#isInfinite() Infinity} that value is written to JSON, even though the JSON specification
405+
* does not permit these values.
406+
*
407+
* <p>To customize formatting or to directly write to an {@link Appendable} instead of creating an
408+
* intermediate {@code String} first, use {@link Gson#toJson(JsonElement, Appendable)
409+
* Gson.toJson(JsonElement, ...)}.
410+
*
411+
* <p>To get the contained String value (without enclosing {@code "} and without escaping), use
412+
* {@link #getAsString()} instead:
413+
*
414+
* <pre>
415+
* JsonPrimitive jsonPrimitive = new JsonPrimitive("with \" quote");
416+
* String json = jsonPrimitive.toString();
417+
* // json: "with \" quote"
418+
* String value = jsonPrimitive.getAsString();
419+
* // value: with " quote
420+
* </pre>
421+
*/
324422
@Override
325423
public String toString() {
326424
try {

gson/src/main/java/com/google/gson/JsonObject.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import java.util.Set;
2323

2424
/**
25-
* A class representing an object type in Json. An object consists of name-value pairs where names
25+
* A class representing an object type in JSON. An object consists of name-value pairs where names
2626
* are strings, and values are any other type of {@link JsonElement}. This allows for a creating a
2727
* tree of JsonElements. The member elements of this object are maintained in order they were added.
2828
* This class does not support {@code null} values. If {@code null} is provided as value argument to
@@ -31,6 +31,9 @@
3131
* <p>{@code JsonObject} does not implement the {@link Map} interface, but a {@code Map} view of it
3232
* can be obtained with {@link #asMap()}.
3333
*
34+
* <p>See the {@link JsonElement} documentation for details on how to convert {@code JsonObject} and
35+
* generally any {@code JsonElement} from and to JSON.
36+
*
3437
* @author Inderjeet Singh
3538
* @author Joel Leitch
3639
*/

gson/src/main/java/com/google/gson/JsonParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
* following example demonstrates how to achieve it:
5151
*
5252
* <pre>
53-
* String json = "{\"skipObj\": {\"skipKey\": \"skipValue\"},\"obj\": {\"key\": \"value\"}}";
53+
* String json = "{\"skipObj\": {\"skipKey\": \"skipValue\"}, \"obj\": {\"key\": \"value\"}}";
5454
* try (JsonReader jsonReader = new JsonReader(new StringReader(json))) {
5555
* jsonReader.beginObject();
5656
* while (jsonReader.hasNext()) {

gson/src/main/java/com/google/gson/JsonPrimitive.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
* A class representing a JSON primitive value. A primitive value is either a String, a Java
2727
* primitive, or a Java primitive wrapper type.
2828
*
29+
* <p>See the {@link JsonElement} documentation for details on how to convert {@code JsonPrimitive}
30+
* and generally any {@code JsonElement} from and to JSON.
31+
*
2932
* @author Inderjeet Singh
3033
* @author Joel Leitch
3134
*/

gson/src/test/java/com/google/gson/JsonArrayTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,4 +376,21 @@ public void testSameAddition() {
376376
assertThat(jsonArray.toString())
377377
.isEqualTo("[\"a\",\"a\",true,true,1212,1212,34.34,34.34,null,null]");
378378
}
379+
380+
@Test
381+
public void testToString() {
382+
JsonArray array = new JsonArray();
383+
assertThat(array.toString()).isEqualTo("[]");
384+
385+
array.add(JsonNull.INSTANCE);
386+
array.add(Float.NaN);
387+
array.add("a\0");
388+
JsonArray nestedArray = new JsonArray();
389+
nestedArray.add('"');
390+
array.add(nestedArray);
391+
JsonObject nestedObject = new JsonObject();
392+
nestedObject.addProperty("n\0", 1);
393+
array.add(nestedObject);
394+
assertThat(array.toString()).isEqualTo("[null,NaN,\"a\\u0000\",[\"\\\"\"],{\"n\\u0000\":1}]");
395+
}
379396
}

gson/src/test/java/com/google/gson/JsonNullTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,9 @@ public void testDeepCopy() {
4343
assertThat(a.deepCopy()).isSameInstanceAs(JsonNull.INSTANCE);
4444
assertThat(JsonNull.INSTANCE.deepCopy()).isSameInstanceAs(JsonNull.INSTANCE);
4545
}
46+
47+
@Test
48+
public void testToString() {
49+
assertThat(JsonNull.INSTANCE.toString()).isEqualTo("null");
50+
}
4651
}

gson/src/test/java/com/google/gson/JsonObjectTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,4 +344,21 @@ public void testEntrySet() {
344344
assertThat(new ArrayList<>(o.entrySet())).isEqualTo(new ArrayList<>(expectedEntriesQueue));
345345
}
346346
}
347+
348+
@Test
349+
public void testToString() {
350+
JsonObject object = new JsonObject();
351+
assertThat(object.toString()).isEqualTo("{}");
352+
353+
object.add("a", JsonNull.INSTANCE);
354+
object.addProperty("b\0", Float.NaN);
355+
JsonArray nestedArray = new JsonArray();
356+
nestedArray.add('"');
357+
object.add("c", nestedArray);
358+
JsonObject nestedObject = new JsonObject();
359+
nestedObject.addProperty("n\0", 1);
360+
object.add("d", nestedObject);
361+
assertThat(object.toString())
362+
.isEqualTo("{\"a\":null,\"b\\u0000\":NaN,\"c\":[\"\\\"\"],\"d\":{\"n\\u0000\":1}}");
363+
}
347364
}

gson/src/test/java/com/google/gson/JsonPrimitiveTest.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,34 @@ public void testDoubleEqualsBigDecimal() {
260260
}
261261

262262
@Test
263-
public void testValidJsonOnToString() {
263+
public void testToString() {
264264
JsonPrimitive json = new JsonPrimitive("Some\nEscaped\nValue");
265265
assertThat(json.toString()).isEqualTo("\"Some\\nEscaped\\nValue\"");
266266

267+
json = new JsonPrimitive("");
268+
assertThat(json.toString()).isEqualTo("\"\"");
269+
267270
json = new JsonPrimitive(new BigDecimal("1.333"));
268271
assertThat(json.toString()).isEqualTo("1.333");
272+
273+
// Preserves trailing 0
274+
json = new JsonPrimitive(new BigDecimal("1.0000"));
275+
assertThat(json.toString()).isEqualTo("1.0000");
276+
277+
json = new JsonPrimitive(Float.NaN);
278+
assertThat(json.toString()).isEqualTo("NaN");
279+
280+
json = new JsonPrimitive(Double.NEGATIVE_INFINITY);
281+
assertThat(json.toString()).isEqualTo("-Infinity");
282+
283+
json = new JsonPrimitive('a');
284+
assertThat(json.toString()).isEqualTo("\"a\"");
285+
286+
json = new JsonPrimitive('\0');
287+
assertThat(json.toString()).isEqualTo("\"\\u0000\"");
288+
289+
json = new JsonPrimitive(true);
290+
assertThat(json.toString()).isEqualTo("true");
269291
}
270292

271293
@Test

0 commit comments

Comments
 (0)