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; package com.google.gson;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
@ -110,6 +111,7 @@ public final class Gson {
private final JsonFormatter formatter; private final JsonFormatter formatter;
private final boolean serializeNulls; private final boolean serializeNulls;
private final boolean htmlSafe;
private final boolean generateNonExecutableJson; private final boolean generateNonExecutableJson;
@ -151,7 +153,7 @@ public final class Gson {
this(DEFAULT_EXCLUSION_STRATEGY, DEFAULT_EXCLUSION_STRATEGY, DEFAULT_NAMING_POLICY, this(DEFAULT_EXCLUSION_STRATEGY, DEFAULT_EXCLUSION_STRATEGY, DEFAULT_NAMING_POLICY,
new MappedObjectConstructor(DefaultTypeAdapters.getDefaultInstanceCreators()), new MappedObjectConstructor(DefaultTypeAdapters.getDefaultInstanceCreators()),
DEFAULT_JSON_FORMATTER, false, DefaultTypeAdapters.getDefaultSerializers(), DEFAULT_JSON_FORMATTER, false, DefaultTypeAdapters.getDefaultSerializers(),
DefaultTypeAdapters.getDefaultDeserializers(), DEFAULT_JSON_NON_EXECUTABLE); DefaultTypeAdapters.getDefaultDeserializers(), DEFAULT_JSON_NON_EXECUTABLE, true);
} }
Gson(ExclusionStrategy serializationStrategy, ExclusionStrategy deserializationStrategy, Gson(ExclusionStrategy serializationStrategy, ExclusionStrategy deserializationStrategy,
@ -159,7 +161,7 @@ public final class Gson {
JsonFormatter formatter, boolean serializeNulls, JsonFormatter formatter, boolean serializeNulls,
ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers, ParameterizedTypeHandlerMap<JsonSerializer<?>> serializers,
ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers, ParameterizedTypeHandlerMap<JsonDeserializer<?>> deserializers,
boolean generateNonExecutableGson) { boolean generateNonExecutableGson, boolean htmlSafe) {
this.serializationStrategy = serializationStrategy; this.serializationStrategy = serializationStrategy;
this.deserializationStrategy = deserializationStrategy; this.deserializationStrategy = deserializationStrategy;
this.fieldNamingPolicy = fieldNamingPolicy; this.fieldNamingPolicy = fieldNamingPolicy;
@ -169,6 +171,7 @@ public final class Gson {
this.serializers = serializers; this.serializers = serializers;
this.deserializers = deserializers; this.deserializers = deserializers;
this.generateNonExecutableJson = generateNonExecutableGson; this.generateNonExecutableJson = generateNonExecutableGson;
this.htmlSafe = htmlSafe;
} }
private ObjectNavigatorFactory createDefaultObjectNavigatorFactory(ExclusionStrategy strategy) { private ObjectNavigatorFactory createDefaultObjectNavigatorFactory(ExclusionStrategy strategy) {
@ -268,7 +271,7 @@ public final class Gson {
*/ */
public String toJson(Object src, Type typeOfSrc) { public String toJson(Object src, Type typeOfSrc) {
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
toJson(src, typeOfSrc, writer); toJson(toJsonTree(src, typeOfSrc), writer);
return writer.toString(); return writer.toString();
} }
@ -317,6 +320,14 @@ public final class Gson {
toJson(jsonElement, writer); 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. * Converts a tree of {@link JsonElement}s into its equivalent JSON representation.
* *
@ -342,15 +353,30 @@ public final class Gson {
if (generateNonExecutableJson) { if (generateNonExecutableJson) {
writer.append(JSON_NON_EXECUTABLE_PREFIX); writer.append(JSON_NON_EXECUTABLE_PREFIX);
} }
if (jsonElement == null && serializeNulls) { toJson(jsonElement, new JsonWriter(Streams.writerForAppendable(writer)));
writeOutNullString(writer);
}
formatter.format(jsonElement, writer, serializeNulls);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(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 * 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 * 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 * @since 1.2
*/ */
public <T> T fromJson(Reader json, Class<T> classOfT) throws JsonParseException { 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; 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 * @throws JsonParseException if json is not a valid representation for an object of type typeOfT
* @since 1.2 * @since 1.2
*/ */
@SuppressWarnings("unchecked")
public <T> T fromJson(Reader json, Type typeOfT) throws JsonParseException { public <T> T fromJson(Reader json, Type typeOfT) throws JsonParseException {
JsonReader jsonReader = new JsonReader(json); return this.<T>fromJson(new JsonReader(json), typeOfT);
jsonReader.setLenient(true); }
JsonElement root = Streams.parse(jsonReader);
return (T) fromJson(root, 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); new JsonPrintFormatter(escapeHtmlChars) : new JsonCompactFormatter(escapeHtmlChars);
Gson gson = new Gson(serializationExclusionStrategy, deserializationExclusionStrategy, Gson gson = new Gson(serializationExclusionStrategy, deserializationExclusionStrategy,
fieldNamingPolicy, objConstructor, formatter, serializeNulls, customSerializers, fieldNamingPolicy, objConstructor, formatter, serializeNulls, customSerializers,
customDeserializers, generateNonExecutableJson); customDeserializers, generateNonExecutableJson, escapeHtmlChars);
return gson; return gson;
} }

View File

@ -50,10 +50,20 @@ public final class JsonParser {
* @since 1.3 * @since 1.3
*/ */
public JsonElement parse(Reader json) throws JsonParseException { 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 { try {
JsonReader jsonReader = new JsonReader(json); return Streams.parse(json);
jsonReader.setLenient(true);
return Streams.parse(jsonReader);
} catch (StackOverflowError e) { } catch (StackOverflowError e) {
throw new JsonParseException("Failed parsing JSON source: " + json + " to Json", e); throw new JsonParseException("Failed parsing JSON source: " + json + " to Json", e);
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError e) {
@ -64,6 +74,8 @@ public final class JsonParser {
} else { } else {
throw e; throw e;
} }
} finally {
json.setLenient(lenient);
} }
} }
} }

View File

@ -142,6 +142,8 @@ public final class JsonWriter implements Closeable {
private boolean lenient; private boolean lenient;
private boolean htmlSafe;
/** /**
* Creates a new instance that writes a JSON-encoded stream to {@code out}. * Creates a new instance that writes a JSON-encoded stream to {@code out}.
* For best performance, ensure {@link Writer} is buffered; wrapping in * For best performance, ensure {@link Writer} is buffered; wrapping in
@ -188,6 +190,32 @@ public final class JsonWriter implements Closeable {
this.lenient = lenient; 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 * Begins encoding a new array. Each call to this method must be paired with
* a call to {@link #endArray}. * a call to {@link #endArray}.
@ -430,6 +458,18 @@ public final class JsonWriter implements Closeable {
out.write("\\f"); out.write("\\f");
break; break;
case '<':
case '>':
case '&':
case '=':
case '\'':
if (htmlSafe) {
out.write(String.format("\\u%04x", (int) c));
} else {
out.write(c);
}
break;
default: default:
if (c <= 0x1F) { if (c <= 0x1F) {
out.write(String.format("\\u%04x", (int) c)); 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, Gson gson = new Gson(exclusionStrategy, exclusionStrategy, Gson.DEFAULT_NAMING_POLICY,
new MappedObjectConstructor(DefaultTypeAdapters.getDefaultInstanceCreators()), new MappedObjectConstructor(DefaultTypeAdapters.getDefaultInstanceCreators()),
Gson.DEFAULT_JSON_FORMATTER, false, DefaultTypeAdapters.getDefaultSerializers(), 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() { assertEquals("{}", gson.toJson(new ClassWithNoFields() {
// empty anonymous class // 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("'"); strings.add("'");
strings.add("\""); strings.add("\"");
assertEquals("[\"\\u003c\",\"\\u003e\",\"\\u003d\",\"\\u0026\",\"'\",\"\\\"\"]", assertEquals("[\"\\u003c\",\"\\u003e\",\"\\u003d\",\"\\u0026\",\"\\u0027\",\"\\\"\"]",
gson.toJson(strings)); gson.toJson(strings));
} }

View File

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

View File

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