Skip to content

Commit

Permalink
Guarantee that JsonElement.toString() produces JSON (google#2659)
Browse files Browse the repository at this point in the history
* Extend `JsonElement` documentation

* Add space to JsonParser code example

* Guarantee that `JsonElement.toString()` produces JSON

* Refer to `JsonElement` documentation from subclasses
  • Loading branch information
Marcono1234 authored and tibor-universe committed Aug 17, 2024
1 parent e338255 commit 2964217
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 4 deletions.
3 changes: 3 additions & 0 deletions gson/src/main/java/com/google/gson/JsonArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
* <p>{@code JsonArray} only implements the {@link Iterable} interface but not the {@link List}
* interface. A {@code List} view of it can be obtained with {@link #asList()}.
*
* <p>See the {@link JsonElement} documentation for details on how to convert {@code JsonArray} and
* generally any {@code JsonElement} from and to JSON.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
Expand Down
100 changes: 99 additions & 1 deletion gson/src/main/java/com/google/gson/JsonElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
Expand All @@ -28,6 +29,69 @@
* A class representing an element of JSON. It could either be a {@link JsonObject}, a {@link
* JsonArray}, a {@link JsonPrimitive} or a {@link JsonNull}.
*
* <p>This class provides multiple {@code getAs} methods which allow
*
* <ul>
* <li>obtaining the represented primitive value, for example {@link #getAsString()}
* <li>casting to the {@code JsonElement} subclasses in a convenient way, for example {@link
* #getAsJsonObject()}
* </ul>
*
* <h2>Converting {@code JsonElement} from / to JSON</h2>
*
* There are two ways to parse JSON data as a {@link JsonElement}:
*
* <ul>
* <li>{@link JsonParser}, for example:
* <pre>
* JsonObject jsonObject = JsonParser.parseString("{}").getAsJsonObject();
* </pre>
* <li>{@link Gson#fromJson(Reader, Class) Gson.fromJson(..., JsonElement.class)}<br>
* It is possible to directly specify a {@code JsonElement} subclass, for example:
* <pre>
* JsonObject jsonObject = gson.fromJson("{}", JsonObject.class);
* </pre>
* </ul>
*
* To convert a {@code JsonElement} to JSON either {@link #toString() JsonElement.toString()} or the
* method {@link Gson#toJson(JsonElement)} and its overloads can be used.
*
* <p>It is also possible to obtain the {@link TypeAdapter} for {@code JsonElement} from a {@link
* Gson} instance and then use it for conversion from and to JSON:
*
* <pre>{@code
* TypeAdapter<JsonElement> adapter = gson.getAdapter(JsonElement.class);
*
* JsonElement value = adapter.fromJson("{}");
* String json = adapter.toJson(value);
* }</pre>
*
* <h2>{@code JsonElement} as JSON data</h2>
*
* {@code JsonElement} can also be treated as JSON data, allowing to deserialize from a {@code
* JsonElement} and serializing to a {@code JsonElement}. The {@link Gson} class offers these
* methods for this:
*
* <ul>
* <li>{@link Gson#fromJson(JsonElement, Class) Gson.fromJson(JsonElement, ...)}, for example:
* <pre>
* JsonObject jsonObject = ...;
* MyClass myObj = gson.fromJson(jsonObject, MyClass.class);
* </pre>
* <li>{@link Gson#toJsonTree(Object)}, for example:
* <pre>
* MyClass myObj = ...;
* JsonElement json = gson.toJsonTree(myObj);
* </pre>
* </ul>
*
* The {@link TypeAdapter} class provides corresponding methods as well:
*
* <ul>
* <li>{@link TypeAdapter#fromJsonTree(JsonElement)}
* <li>{@link TypeAdapter#toJsonTree(Object)}
* </ul>
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
Expand Down Expand Up @@ -320,7 +384,41 @@ public short getAsShort() {
throw new UnsupportedOperationException(getClass().getSimpleName());
}

/** Returns a String representation of this element. */
/**
* Converts this element to a JSON string.
*
* <p>For example:
*
* <pre>
* JsonObject object = new JsonObject();
* object.add("a", JsonNull.INSTANCE);
* JsonArray array = new JsonArray();
* array.add(1);
* object.add("b", array);
*
* String json = object.toString();
* // json: {"a":null,"b":[1]}
* </pre>
*
* If this element or any nested elements contain {@link Double#NaN NaN} or {@link
* Double#isInfinite() Infinity} that value is written to JSON, even though the JSON specification
* does not permit these values.
*
* <p>To customize formatting or to directly write to an {@link Appendable} instead of creating an
* intermediate {@code String} first, use {@link Gson#toJson(JsonElement, Appendable)
* Gson.toJson(JsonElement, ...)}.
*
* <p>To get the contained String value (without enclosing {@code "} and without escaping), use
* {@link #getAsString()} instead:
*
* <pre>
* JsonPrimitive jsonPrimitive = new JsonPrimitive("with \" quote");
* String json = jsonPrimitive.toString();
* // json: "with \" quote"
* String value = jsonPrimitive.getAsString();
* // value: with " quote
* </pre>
*/
@Override
public String toString() {
try {
Expand Down
5 changes: 4 additions & 1 deletion gson/src/main/java/com/google/gson/JsonObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import java.util.Set;

/**
* A class representing an object type in Json. An object consists of name-value pairs where names
* A class representing an object type in JSON. An object consists of name-value pairs where names
* are strings, and values are any other type of {@link JsonElement}. This allows for a creating a
* tree of JsonElements. The member elements of this object are maintained in order they were added.
* This class does not support {@code null} values. If {@code null} is provided as value argument to
Expand All @@ -31,6 +31,9 @@
* <p>{@code JsonObject} does not implement the {@link Map} interface, but a {@code Map} view of it
* can be obtained with {@link #asMap()}.
*
* <p>See the {@link JsonElement} documentation for details on how to convert {@code JsonObject} and
* generally any {@code JsonElement} from and to JSON.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
Expand Down
2 changes: 1 addition & 1 deletion gson/src/main/java/com/google/gson/JsonParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
* following example demonstrates how to achieve it:
*
* <pre>
* String json = "{\"skipObj\": {\"skipKey\": \"skipValue\"},\"obj\": {\"key\": \"value\"}}";
* String json = "{\"skipObj\": {\"skipKey\": \"skipValue\"}, \"obj\": {\"key\": \"value\"}}";
* try (JsonReader jsonReader = new JsonReader(new StringReader(json))) {
* jsonReader.beginObject();
* while (jsonReader.hasNext()) {
Expand Down
3 changes: 3 additions & 0 deletions gson/src/main/java/com/google/gson/JsonPrimitive.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
* A class representing a JSON primitive value. A primitive value is either a String, a Java
* primitive, or a Java primitive wrapper type.
*
* <p>See the {@link JsonElement} documentation for details on how to convert {@code JsonPrimitive}
* and generally any {@code JsonElement} from and to JSON.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
Expand Down
17 changes: 17 additions & 0 deletions gson/src/test/java/com/google/gson/JsonArrayTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -379,4 +379,21 @@ public void testSameAddition() {
assertThat(jsonArray.toString())
.isEqualTo("[\"a\",\"a\",true,true,1212,1212,34.34,34.34,null,null]");
}

@Test
public void testToString() {
JsonArray array = new JsonArray();
assertThat(array.toString()).isEqualTo("[]");

array.add(JsonNull.INSTANCE);
array.add(Float.NaN);
array.add("a\0");
JsonArray nestedArray = new JsonArray();
nestedArray.add('"');
array.add(nestedArray);
JsonObject nestedObject = new JsonObject();
nestedObject.addProperty("n\0", 1);
array.add(nestedObject);
assertThat(array.toString()).isEqualTo("[null,NaN,\"a\\u0000\",[\"\\\"\"],{\"n\\u0000\":1}]");
}
}
5 changes: 5 additions & 0 deletions gson/src/test/java/com/google/gson/JsonNullTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,9 @@ public void testDeepCopy() {
assertThat(a.deepCopy()).isSameInstanceAs(JsonNull.INSTANCE);
assertThat(JsonNull.INSTANCE.deepCopy()).isSameInstanceAs(JsonNull.INSTANCE);
}

@Test
public void testToString() {
assertThat(JsonNull.INSTANCE.toString()).isEqualTo("null");
}
}
17 changes: 17 additions & 0 deletions gson/src/test/java/com/google/gson/JsonObjectTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,21 @@ public void testEntrySet() {
assertThat(new ArrayList<>(o.entrySet())).isEqualTo(new ArrayList<>(expectedEntriesQueue));
}
}

@Test
public void testToString() {
JsonObject object = new JsonObject();
assertThat(object.toString()).isEqualTo("{}");

object.add("a", JsonNull.INSTANCE);
object.addProperty("b\0", Float.NaN);
JsonArray nestedArray = new JsonArray();
nestedArray.add('"');
object.add("c", nestedArray);
JsonObject nestedObject = new JsonObject();
nestedObject.addProperty("n\0", 1);
object.add("d", nestedObject);
assertThat(object.toString())
.isEqualTo("{\"a\":null,\"b\\u0000\":NaN,\"c\":[\"\\\"\"],\"d\":{\"n\\u0000\":1}}");
}
}
24 changes: 23 additions & 1 deletion gson/src/test/java/com/google/gson/JsonPrimitiveTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,34 @@ public void testDoubleEqualsBigDecimal() {
}

@Test
public void testValidJsonOnToString() {
public void testToString() {
JsonPrimitive json = new JsonPrimitive("Some\nEscaped\nValue");
assertThat(json.toString()).isEqualTo("\"Some\\nEscaped\\nValue\"");

json = new JsonPrimitive("");
assertThat(json.toString()).isEqualTo("\"\"");

json = new JsonPrimitive(new BigDecimal("1.333"));
assertThat(json.toString()).isEqualTo("1.333");

// Preserves trailing 0
json = new JsonPrimitive(new BigDecimal("1.0000"));
assertThat(json.toString()).isEqualTo("1.0000");

json = new JsonPrimitive(Float.NaN);
assertThat(json.toString()).isEqualTo("NaN");

json = new JsonPrimitive(Double.NEGATIVE_INFINITY);
assertThat(json.toString()).isEqualTo("-Infinity");

json = new JsonPrimitive('a');
assertThat(json.toString()).isEqualTo("\"a\"");

json = new JsonPrimitive('\0');
assertThat(json.toString()).isEqualTo("\"\\u0000\"");

json = new JsonPrimitive(true);
assertThat(json.toString()).isEqualTo("true");
}

@Test
Expand Down

0 comments on commit 2964217

Please sign in to comment.