Support mixed streaming and databinding with new APIs.

This commit is contained in:
Jesse Wilson 2010-09-02 00:15:23 +00:00
parent ffdf0e7012
commit 747e3c3051
9 changed files with 212 additions and 42 deletions

View File

@ -17,6 +17,7 @@
package com.google.gson;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
@ -110,6 +111,7 @@ public final class Gson {
private final JsonFormatter formatter;
private final boolean serializeNulls;
private final boolean htmlSafe;
private final boolean generateNonExecutableJson;
@ -151,7 +153,7 @@ public final class Gson {
this(DEFAULT_EXCLUSION_STRATEGY, DEFAULT_EXCLUSION_STRATEGY, DEFAULT_NAMING_POLICY,
new MappedObjectConstructor(DefaultTypeAdapters.getDefaultInstanceCreators()),
DEFAULT_JSON_FORMATTER, false, DefaultTypeAdapters.getDefaultSerializers(),
DefaultTypeAdapters.getDefaultDeserializers(), DEFAULT_JSON_NON_EXECUTABLE);
DefaultTypeAdapters.getDefaultDeserializers(), DEFAULT_JSON_NON_EXECUTABLE, true);
}
Gson(ExclusionStrategy serializationStrategy, ExclusionStrategy deserializationStrategy,
@ -159,7 +161,7 @@ public final class Gson {
JsonFormatter formatter, boolean serializeNulls,
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers,
ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers,
boolean generateNonExecutableGson) {
boolean generateNonExecutableGson, boolean htmlSafe) {
this.serializationStrategy = serializationStrategy;
this.deserializationStrategy = deserializationStrategy;
this.fieldNamingPolicy = fieldNamingPolicy;
@ -169,6 +171,7 @@ public final class Gson {
this.serializers = serializers;
this.deserializers = deserializers;
this.generateNonExecutableJson = generateNonExecutableGson;
this.htmlSafe = htmlSafe;
}
private ObjectNavigatorFactory createDefaultObjectNavigatorFactory(ExclusionStrategy strategy) {
@ -268,7 +271,7 @@ public final class Gson {
*/
public String toJson(Object src, Type typeOfSrc) {
StringWriter writer = new StringWriter();
toJson(src, typeOfSrc, writer);
toJson(toJsonTree(src, typeOfSrc), writer);
return writer.toString();
}
@ -317,6 +320,14 @@ public final class Gson {
toJson(jsonElement, writer);
}
/**
* Writes the JSON representation of {@code src} of type {@code typeOfSrc} to
* {@code writer}.
*/
public void toJson(Object src, Type typeOfSrc, JsonWriter writer) {
toJson(toJsonTree(src, typeOfSrc), writer);
}
/**
* Converts a tree of {@link JsonElement}s into its equivalent JSON representation.
*
@ -342,15 +353,30 @@ public final class Gson {
if (generateNonExecutableJson) {
writer.append(JSON_NON_EXECUTABLE_PREFIX);
}
if (jsonElement == null && serializeNulls) {
writeOutNullString(writer);
}
formatter.format(jsonElement, writer, serializeNulls);
toJson(jsonElement, new JsonWriter(Streams.writerForAppendable(writer)));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Writes the JSON for {@code jsonElement} to {@code writer}.
*/
public void toJson(JsonElement jsonElement, JsonWriter writer) {
boolean oldLenient = writer.isLenient();
writer.setLenient(true);
boolean oldHtmlSafe = writer.isHtmlSafe();
writer.setHtmlSafe(htmlSafe);
try {
Streams.write(jsonElement, serializeNulls, writer);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
writer.setLenient(oldLenient);
writer.setHtmlSafe(oldHtmlSafe);
}
}
/**
* This method deserializes the specified Json into an object of the specified class. It is not
* suitable to use if the specified class is a generic type since it will not have the generic
@ -417,7 +443,7 @@ public final class Gson {
* @since 1.2
*/
public <T> T fromJson(Reader json, Class<T> classOfT) throws JsonParseException {
T target = classOfT.cast(fromJson(json, (Type) classOfT));
T target = classOfT.cast(fromJson(new JsonReader(json), classOfT));
return target;
}
@ -439,12 +465,24 @@ public final class Gson {
* @throws JsonParseException if json is not a valid representation for an object of type typeOfT
* @since 1.2
*/
@SuppressWarnings("unchecked")
public <T> T fromJson(Reader json, Type typeOfT) throws JsonParseException {
JsonReader jsonReader = new JsonReader(json);
jsonReader.setLenient(true);
JsonElement root = Streams.parse(jsonReader);
return (T) fromJson(root, typeOfT);
return this.<T>fromJson(new JsonReader(json), typeOfT);
}
/**
* Reads the next JSON value from {@code reader} and convert it to an object
* of type {@code typeOfT}.
*/
@SuppressWarnings("unchecked") // this method is unsafe and should be used very carefully
public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonParseException {
boolean oldLenient = reader.isLenient();
reader.setLenient(true);
try {
JsonElement root = Streams.parse(reader);
return (T) fromJson(root, typeOfT);
} finally {
reader.setLenient(oldLenient);
}
}
/**

View File

@ -546,7 +546,7 @@ public final class GsonBuilder {
new JsonPrintFormatter(escapeHtmlChars) : new JsonCompactFormatter(escapeHtmlChars);
Gson gson = new Gson(serializationExclusionStrategy, deserializationExclusionStrategy,
fieldNamingPolicy, objConstructor, formatter, serializeNulls, customSerializers,
customDeserializers, generateNonExecutableJson);
customDeserializers, generateNonExecutableJson, escapeHtmlChars);
return gson;
}

View File

@ -50,10 +50,20 @@ public final class JsonParser {
* @since 1.3
*/
public JsonElement parse(Reader json) throws JsonParseException {
return parse(new JsonReader(json));
}
/**
* Returns the next value from the JSON stream as a parse tree.
*
* @throws JsonParseException if there is an IOException or if the specified
* text is not valid JSON
*/
public JsonElement parse(JsonReader json) throws JsonParseException {
boolean lenient = json.isLenient();
json.setLenient(true);
try {
JsonReader jsonReader = new JsonReader(json);
jsonReader.setLenient(true);
return Streams.parse(jsonReader);
return Streams.parse(json);
} catch (StackOverflowError e) {
throw new JsonParseException("Failed parsing JSON source: " + json + " to Json", e);
} catch (OutOfMemoryError e) {
@ -64,6 +74,8 @@ public final class JsonParser {
} else {
throw e;
}
} finally {
json.setLenient(lenient);
}
}
}
}

View File

@ -142,6 +142,8 @@ public final class JsonWriter implements Closeable {
private boolean lenient;
private boolean htmlSafe;
/**
* Creates a new instance that writes a JSON-encoded stream to {@code out}.
* For best performance, ensure {@link Writer} is buffered; wrapping in
@ -188,6 +190,32 @@ public final class JsonWriter implements Closeable {
this.lenient = lenient;
}
/**
* Returns true if this writer has relaxed syntax rules.
*/
public boolean isLenient() {
return lenient;
}
/**
* Configure this writer to emit JSON that's safe for direct inclusion in HTML
* and XML documents. This escapes the HTML characters {@code <}, {@code >},
* {@code &} and {@code =} before writing them to the stream. Without this
* setting, your XML/HTML encoder should replace these characters with the
* corresponding escape sequences.
*/
public void setHtmlSafe(boolean htmlSafe) {
this.htmlSafe = htmlSafe;
}
/**
* Returns true if this writer writes JSON that's safe for inclusion in HTML
* and XML documents.
*/
public boolean isHtmlSafe() {
return htmlSafe;
}
/**
* Begins encoding a new array. Each call to this method must be paired with
* a call to {@link #endArray}.
@ -430,6 +458,18 @@ public final class JsonWriter implements Closeable {
out.write("\\f");
break;
case '<':
case '>':
case '&':
case '=':
case '\'':
if (htmlSafe) {
out.write(String.format("\\u%04x", (int) c));
} else {
out.write(c);
}
break;
default:
if (c <= 0x1F) {
out.write(String.format("\\u%04x", (int) c));

View File

@ -42,7 +42,7 @@ public class FunctionWithInternalDependenciesTest extends TestCase {
Gson gson = new Gson(exclusionStrategy, exclusionStrategy, Gson.DEFAULT_NAMING_POLICY,
new MappedObjectConstructor(DefaultTypeAdapters.getDefaultInstanceCreators()),
Gson.DEFAULT_JSON_FORMATTER, false, DefaultTypeAdapters.getDefaultSerializers(),
DefaultTypeAdapters.getDefaultDeserializers(), Gson.DEFAULT_JSON_NON_EXECUTABLE);
DefaultTypeAdapters.getDefaultDeserializers(), Gson.DEFAULT_JSON_NON_EXECUTABLE, true);
assertEquals("{}", gson.toJson(new ClassWithNoFields() {
// empty anonymous class
}));

View File

@ -0,0 +1,94 @@
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import junit.framework.TestCase;
public final class MixedStreamTest extends TestCase {
private static final Car BLUE_MUSTANG = new Car("mustang", 0x0000FF);
private static final Car BLACK_BMW = new Car("bmw", 0x000000);
private static final Car RED_MIATA = new Car("miata", 0xFF0000);
private static final String CARS_JSON = "[\n"
+ " {\n"
+ " \"name\": \"mustang\",\n"
+ " \"color\": 255\n"
+ " },\n"
+ " {\n"
+ " \"name\": \"bmw\",\n"
+ " \"color\": 0\n"
+ " },\n"
+ " {\n"
+ " \"name\": \"miata\",\n"
+ " \"color\": 16711680\n"
+ " }\n"
+ "]";
public void testWriteMixedStreamed() throws IOException {
Gson gson = new Gson();
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray();
jsonWriter.setIndent(" ");
gson.toJson(BLUE_MUSTANG, Car.class, jsonWriter);
gson.toJson(BLACK_BMW, Car.class, jsonWriter);
gson.toJson(RED_MIATA, Car.class, jsonWriter);
jsonWriter.endArray();
assertEquals(CARS_JSON, stringWriter.toString());
}
public void testReadMixedStreamed() throws IOException {
Gson gson = new Gson();
StringReader stringReader = new StringReader(CARS_JSON);
JsonReader jsonReader = new JsonReader(stringReader);
jsonReader.beginArray();
assertEquals(BLUE_MUSTANG, gson.fromJson(jsonReader, Car.class));
assertEquals(BLACK_BMW, gson.fromJson(jsonReader, Car.class));
assertEquals(RED_MIATA, gson.fromJson(jsonReader, Car.class));
jsonReader.endArray();
}
static final class Car {
String name;
int color;
Car(String name, int color) {
this.name = name;
this.color = color;
}
Car() {} // for Gson
@Override public int hashCode() {
return name.hashCode() ^ color;
}
@Override public boolean equals(Object o) {
return o instanceof Car
&& ((Car) o).name.equals(name)
&& ((Car) o).color == color;
}
}
}

View File

@ -54,7 +54,7 @@ public class EscapingTest extends TestCase {
strings.add("&");
strings.add("'");
strings.add("\"");
assertEquals("[\"\\u003c\",\"\\u003e\",\"\\u003d\",\"\\u0026\",\"'\",\"\\\"\"]",
assertEquals("[\"\\u003c\",\"\\u003e\",\"\\u003d\",\"\\u0026\",\"\\u0027\",\"\\\"\"]",
gson.toJson(strings));
}

View File

@ -131,7 +131,7 @@ public class NullObjectAndFieldTest extends TestCase {
gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.setPrettyPrinting().create();
String result = gson.toJson(new ClassWithMembers());
assertEquals("{}\n", result);
assertEquals("{}", result);
gson = gsonBuilder.serializeNulls().create();
result = gson.toJson(new ClassWithMembers());
@ -142,11 +142,11 @@ public class NullObjectAndFieldTest extends TestCase {
gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.setPrettyPrinting().create();
String result = gson.toJson(new String[] { "1", null, "3" });
assertEquals("[\"1\",null,\"3\"]\n", result);
assertEquals("[\"1\",null,\"3\"]", result);
gson = gsonBuilder.serializeNulls().create();
result = gson.toJson(new String[] { "1", null, "3" });
assertEquals("[\"1\",null,\"3\"]\n", result);
assertEquals("[\"1\",null,\"3\"]", result);
}
private static class ClassWithNullWrappedPrimitive {

View File

@ -59,34 +59,32 @@ public class PrettyPrintingTest extends TestCase {
Type typeOfSrc = new TypeToken<List<BagOfPrimitives>>() {}.getType();
String json = gson.toJson(listOfB, typeOfSrc);
print(json);
assertPrintMargin(json);
}
public void testPrettyPrintArrayOfObjects() {
ArrayOfObjects target = new ArrayOfObjects();
String json = gson.toJson(target);
print(json);
assertPrintMargin(json);
}
public void testPrettyPrintArrayOfPrimitives() {
int[] ints = new int[] { 1, 2, 3, 4, 5 };
String json = gson.toJson(ints);
assertEquals("[1,2,3,4,5]\n", json);
assertEquals("[1,2,3,4,5]", json);
}
public void testPrettyPrintArrayOfPrimitiveArrays() {
int[][] ints = new int[][] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 },
{ 9, 0 }, { 10 } };
String json = gson.toJson(ints);
assertEquals("[[1,2],[3,4],[5,6],[7,8],[9,0],[10]]\n", json);
assertEquals("[[1,2],[3,4],[5,6],[7,8],[9,0],[10]]", json);
}
public void testPrettyPrintListOfPrimitiveArrays() {
List<Integer[]> list = Arrays.asList(new Integer[][] { { 1, 2 }, { 3, 4 },
{ 5, 6 }, { 7, 8 }, { 9, 0 }, { 10 } });
String json = gson.toJson(list);
assertEquals("[[1,2],[3,4],[5,6],[7,8],[9,0],[10]]\n", json);
assertEquals("[[1,2],[3,4],[5,6],[7,8],[9,0],[10]]", json);
}
public void testMap() {
@ -94,7 +92,7 @@ public class PrettyPrintingTest extends TestCase {
map.put("abc", 1);
map.put("def", 5);
String json = gson.toJson(map);
assertEquals("{\"abc\":1,\"def\":5}\n", json);
assertEquals("{\"abc\":1,\"def\":5}", json);
}
// In response to bug 153
@ -114,7 +112,7 @@ public class PrettyPrintingTest extends TestCase {
public void testMultipleArrays() {
int[][][] ints = new int[][][] { { { 1 }, { 2 } } };
String json = gson.toJson(ints);
assertEquals("[[[1],[2]]]\n", json);
assertEquals("[[[1],[2]]]", json);
}
private void print(String msg) {
@ -122,16 +120,4 @@ public class PrettyPrintingTest extends TestCase {
System.out.println(msg);
}
}
private void assertPrintMargin(String str) {
int position = 0;
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; ++i, ++position) {
char c = chars[i];
if (c == '\n') {
position = 0;
}
assertTrue(position <= PRINT_MARGIN - RIGHT_MARGIN + 1);
}
}
}