Ensure that the JsonElement.toString() will always generate valid JSON (even when control characters/whitespace characters are used).

This commit is contained in:
Joel Leitch 2009-09-23 18:28:03 +00:00
parent 3b0f8f4340
commit 50eb582657
11 changed files with 41 additions and 275 deletions

View File

@ -87,7 +87,6 @@ public final class Gson {
private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n";
private final ExclusionStrategy serializationStrategy;
private final ExclusionStrategy deserializationStrategy;

View File

@ -293,7 +293,7 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
}
@Override
protected void toString(Appendable sb) throws IOException {
protected void toString(Appendable sb, Escaper escaper) throws IOException {
sb.append('[');
boolean first = true;
for (JsonElement element : elements) {
@ -302,7 +302,7 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
} else {
sb.append(',');
}
element.toString(sb);
element.toString(sb, escaper);
}
sb.append(']');
}

View File

@ -27,15 +27,17 @@ final class JsonCompactFormatter implements JsonFormatter {
private static class FormattingVisitor implements JsonElementVisitor {
private final Appendable writer;
private final Escaper escaper;
private final boolean serializeNulls;
FormattingVisitor(Appendable writer, boolean serializeNulls) {
FormattingVisitor(Appendable writer, Escaper escaper, boolean serializeNulls) {
this.writer = writer;
this.escaper = escaper;
this.serializeNulls = serializeNulls;
}
public void visitPrimitive(JsonPrimitive primitive) throws IOException {
primitive.toString(writer);
primitive.toString(writer, escaper);
}
public void visitNull() throws IOException {
@ -51,7 +53,7 @@ final class JsonCompactFormatter implements JsonFormatter {
if (!isFirst) {
writer.append(',');
}
member.toString(writer);
member.toString(writer, escaper);
}
public void visitArrayMember(JsonArray parent, JsonArray member,
@ -90,7 +92,7 @@ final class JsonCompactFormatter implements JsonFormatter {
writer.append('"');
writer.append(memberName);
writer.append("\":");
member.toString(writer);
member.toString(writer, escaper);
}
public void visitObjectMember(JsonObject parent, String memberName, JsonArray member,
@ -140,8 +142,8 @@ final class JsonCompactFormatter implements JsonFormatter {
if (root == null) {
return;
}
JsonElementVisitor visitor = new JsonEscapingVisitor(
new FormattingVisitor(writer, serializeNulls), escapeHtmlChars);
JsonElementVisitor visitor = new FormattingVisitor(
writer, new Escaper(escapeHtmlChars), serializeNulls);
JsonTreeNavigator navigator = new JsonTreeNavigator(visitor, serializeNulls);
navigator.navigate(root);
}

View File

@ -28,6 +28,7 @@ import java.math.BigInteger;
* @author Joel Leitch
*/
public abstract class JsonElement {
private static final Escaper BASIC_ESCAPER = new Escaper(false);
/**
* provides check for verifying if this element is an array or not.
@ -330,12 +331,12 @@ public abstract class JsonElement {
public String toString() {
try {
StringBuilder sb = new StringBuilder();
toString(sb);
toString(sb, BASIC_ESCAPER);
return sb.toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected abstract void toString(Appendable sb) throws IOException;
protected abstract void toString(Appendable sb, Escaper escaper) throws IOException;
}

View File

@ -1,68 +0,0 @@
/*
* Copyright (C) 2008 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 java.io.IOException;
/**
* Performs JSON escaping and passes on the new escaped value to the delegate
* {@link JsonElementVisitor}.
*
* @author Joel Leitch
*/
class JsonEscapingVisitor extends DelegatingJsonElementVisitor {
private final Escaper escaper;
/**
* Constructs a Visitor that will properly escape any JSON primitive values.
*
* @param delegate the JsonElementVisitor that this instance will use for delegation
*/
protected JsonEscapingVisitor(JsonElementVisitor delegate, boolean escapeHtmlChars) {
this(delegate, new Escaper(escapeHtmlChars));
}
protected JsonEscapingVisitor(JsonElementVisitor delegate, Escaper escaper) {
super(delegate);
this.escaper = escaper;
}
@Override
public void visitArrayMember(JsonArray parent, JsonPrimitive member,
boolean isFirst) throws IOException {
super.visitArrayMember(parent, escapeJsonPrimitive(member), isFirst);
}
@Override
public void visitObjectMember(JsonObject parent, String memberName, JsonPrimitive member,
boolean isFirst) throws IOException {
super.visitObjectMember(parent, memberName, escapeJsonPrimitive(member), isFirst);
}
@Override
public void visitPrimitive(JsonPrimitive primitive) throws IOException {
super.visitPrimitive(escapeJsonPrimitive(primitive));
}
private JsonPrimitive escapeJsonPrimitive(JsonPrimitive member) {
if (member.isString()) {
String memberValue = member.getAsString();
member.setValue(escaper.escapeJsonString(memberValue));
}
return member;
}
}

View File

@ -36,7 +36,7 @@ public final class JsonNull extends JsonElement {
}
@Override
protected void toString(Appendable sb) throws IOException {
protected void toString(Appendable sb, Escaper escaper) throws IOException {
sb.append("null");
}

View File

@ -191,7 +191,7 @@ public final class JsonObject extends JsonElement {
}
@Override
protected void toString(Appendable sb) throws IOException {
protected void toString(Appendable sb, Escaper escaper) throws IOException {
sb.append('{');
boolean first = true;
for (Map.Entry<String, JsonElement> entry : members.entrySet()) {
@ -203,7 +203,7 @@ public final class JsonObject extends JsonElement {
sb.append('\"');
sb.append(entry.getKey());
sb.append("\":");
entry.getValue().toString(sb);
entry.getValue().toString(sb, escaper);
}
sb.append('}');
}

View File

@ -29,13 +29,11 @@ import java.math.BigInteger;
* @author Joel Leitch
*/
public final class JsonPrimitive extends JsonElement {
private static final Class<?>[] PRIMITIVE_TYPES = { int.class, long.class, short.class,
float.class, double.class, byte.class, boolean.class, char.class, Integer.class, Long.class,
Short.class, Float.class, Double.class, Byte.class, Boolean.class, Character.class };
private static final BigInteger INTEGER_MAX = BigInteger.valueOf(Integer.MAX_VALUE);
private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE);
private Object value;
@ -320,10 +318,10 @@ public final class JsonPrimitive extends JsonElement {
}
@Override
protected void toString(Appendable sb) throws IOException {
if (value instanceof String) {
protected void toString(Appendable sb, Escaper escaper) throws IOException {
if (isString()) {
sb.append('"');
sb.append((String) value);
sb.append(escaper.escapeJsonString(value.toString()));
sb.append('"');
} else {
sb.append(value.toString());

View File

@ -146,11 +146,13 @@ final class JsonPrintFormatter implements JsonFormatter {
private final Map<Integer, Boolean> firstArrayElement;
private final Map<Integer, Boolean> firstObjectMember;
private final JsonWriter writer;
private final Escaper escaper;
private final boolean serializeNulls;
private int level = 0;
PrintFormattingVisitor(JsonWriter writer, boolean serializeNulls) {
PrintFormattingVisitor(JsonWriter writer, Escaper escaper, boolean serializeNulls) {
this.writer = writer;
this.escaper = escaper;
this.serializeNulls = serializeNulls;
this.firstArrayElement = new HashMap<Integer, Boolean>();
this.firstObjectMember = new HashMap<Integer, Boolean>();
@ -172,7 +174,7 @@ final class JsonPrintFormatter implements JsonFormatter {
public void visitArrayMember(JsonArray parent, JsonPrimitive member,
boolean isFirst) throws IOException {
addCommaCheckingFirst(firstArrayElement);
writer.value(member.toString());
writer.value(escapeJsonPrimitive(member));
}
public void visitArrayMember(JsonArray parent, JsonArray member,
@ -204,7 +206,7 @@ final class JsonPrintFormatter implements JsonFormatter {
addCommaCheckingFirst(firstObjectMember);
writer.key(memberName);
writer.fieldSeparator();
writer.value(member.toString());
writer.value(escapeJsonPrimitive(member));
}
public void visitObjectMember(JsonObject parent, String memberName, JsonArray member,
@ -233,12 +235,18 @@ final class JsonPrintFormatter implements JsonFormatter {
}
public void visitPrimitive(JsonPrimitive primitive) throws IOException {
writer.value(primitive.toString());
writer.value(escapeJsonPrimitive(primitive));
}
public void visitNull() throws IOException {
writer.value("null");
}
private String escapeJsonPrimitive(JsonPrimitive member) throws IOException {
StringBuilder builder = new StringBuilder();
member.toString(builder, escaper);
return builder.toString();
}
}
public void format(JsonElement root, Appendable writer,
@ -247,8 +255,8 @@ final class JsonPrintFormatter implements JsonFormatter {
return;
}
JsonWriter jsonWriter = new JsonWriter(writer);
JsonElementVisitor visitor = new JsonEscapingVisitor(
new PrintFormattingVisitor(jsonWriter, serializeNulls), escapeHtmlChars);
JsonElementVisitor visitor = new PrintFormattingVisitor(
jsonWriter, new Escaper(escapeHtmlChars), serializeNulls);
JsonTreeNavigator navigator = new JsonTreeNavigator(visitor, serializeNulls);
navigator.navigate(root);
jsonWriter.finishLine();

View File

@ -1,182 +0,0 @@
/*
* Copyright (C) 2008 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 junit.framework.TestCase;
/**
* Performs some unit testing for the {@link JsonEscapingVisitor} class.
*
* @author Joel Leitch
*/
public class JsonEscapingVisitorTest extends TestCase {
private StubbedJsonElementVisitor stubVisitor;
private JsonEscapingVisitor escapingVisitor;
private Escaper escaper;
@Override
protected void setUp() throws Exception {
super.setUp();
stubVisitor = new StubbedJsonElementVisitor();
escaper = new Escaper(true);
escapingVisitor = new JsonEscapingVisitor(stubVisitor, escaper);
}
public void testNonStringPrimitiveVisitation() throws Exception {
boolean value = true;
JsonPrimitive primitive = new JsonPrimitive(value);
escapingVisitor.visitPrimitive(primitive);
assertEquals(value, stubVisitor.primitiveReceived.getAsBoolean());
}
public void testStringPrimitiveVisitationNoEscapingRequired() throws Exception {
String value = "Testing123";
JsonPrimitive primitive = new JsonPrimitive(value);
escapingVisitor.visitPrimitive(primitive);
assertEquals(value, stubVisitor.primitiveReceived.getAsObject());
}
public void testStringPrimitiveVisitationEscapingRequired() throws Exception {
String value = "Testing\"123";
JsonPrimitive primitive = new JsonPrimitive(value);
escapingVisitor.visitPrimitive(primitive);
assertEquals(escaper.escapeJsonString(value), stubVisitor.primitiveReceived.getAsString());
}
public void testNonStringArrayVisitation() throws Exception {
int value = 123;
JsonPrimitive primitive = new JsonPrimitive(value);
JsonArray array = new JsonArray();
array.add(primitive);
escapingVisitor.visitArrayMember(array, primitive, true);
assertEquals(value, stubVisitor.primitiveReceived.getAsInt());
}
public void testStringArrayVisitationNoEscaping() throws Exception {
String value = "Testing123";
JsonPrimitive primitive = new JsonPrimitive(value);
JsonArray array = new JsonArray();
array.add(primitive);
escapingVisitor.visitArrayMember(array, primitive, true);
assertEquals(value, stubVisitor.primitiveReceived.getAsString());
}
public void testStringArrayVisitationEscapingRequired() throws Exception {
String value = "Testing\"123";
JsonPrimitive primitive = new JsonPrimitive(value);
JsonArray array = new JsonArray();
array.add(primitive);
escapingVisitor.visitArrayMember(array, primitive, true);
assertEquals(escaper.escapeJsonString(value), stubVisitor.primitiveReceived.getAsString());
}
public void testNonStringFieldVisitation() throws Exception {
String fieldName = "fieldName";
int value = 123;
JsonPrimitive primitive = new JsonPrimitive(value);
JsonObject object = new JsonObject();
object.addProperty(fieldName, value);
escapingVisitor.visitObjectMember(object, fieldName, primitive, true);
assertEquals(value, stubVisitor.primitiveReceived.getAsInt());
}
public void testStringFieldVisitationNoEscaping() throws Exception {
String fieldName = "fieldName";
String value = "Testing123";
JsonPrimitive primitive = new JsonPrimitive(value);
JsonObject object = new JsonObject();
object.addProperty(fieldName, value);
escapingVisitor.visitObjectMember(object, fieldName, primitive, true);
assertEquals(value, stubVisitor.primitiveReceived.getAsString());
}
public void testStringFieldVisitationEscapingRequired() throws Exception {
String fieldName = "fieldName";
String value = "Testing\"123";
JsonPrimitive primitive = new JsonPrimitive(value);
JsonObject object = new JsonObject();
object.addProperty(fieldName, value);
escapingVisitor.visitObjectMember(object, fieldName, primitive, true);
assertEquals(escaper.escapeJsonString(value), stubVisitor.primitiveReceived.getAsString());
}
private static class StubbedJsonElementVisitor implements JsonElementVisitor {
public JsonPrimitive primitiveReceived;
public void endArray(JsonArray array) {
// Do nothing
}
public void endObject(JsonObject object) {
// Do nothing
}
public void startArray(JsonArray array) {
// Do nothing
}
public void startObject(JsonObject object) {
// Do nothing
}
public void visitArrayMember(JsonArray parent, JsonPrimitive member, boolean isFirst) {
primitiveReceived = member;
}
public void visitArrayMember(JsonArray parent, JsonArray member, boolean isFirst) {
// Do nothing
}
public void visitArrayMember(JsonArray parent, JsonObject member, boolean isFirst) {
// Do nothing
}
public void visitObjectMember(
JsonObject parent, String memberName, JsonPrimitive member, boolean isFirst) {
primitiveReceived = member;
}
public void visitObjectMember(
JsonObject parent, String memberName, JsonArray member, boolean isFirst) {
// Do nothing
}
public void visitObjectMember(
JsonObject parent, String memberName, JsonObject member, boolean isFirst) {
// Do nothing
}
public void visitPrimitive(JsonPrimitive primitive) {
primitiveReceived = primitive;
}
public void visitNullArrayMember(JsonArray parent, boolean isFirst) {
// Do nothing
}
public void visitNull() {
// Do nothing
}
public void visitNullObjectMember(JsonObject parent, String memberName, boolean isFirst) {
// Do nothing
}
}
}

View File

@ -80,4 +80,12 @@ public class JsonPrimitiveTest extends TestCase {
fail("Integers can not handle exponents like this.");
} catch (NumberFormatException expected) { }
}
public void testValidJsonOnToString() throws Exception {
JsonPrimitive json = new JsonPrimitive("Some\nEscaped\nValue");
assertEquals("\"Some\\nEscaped\\nValue\"", json.toString());
json = new JsonPrimitive(new BigDecimal("1.333"));
assertEquals("1.333", json.toString());
}
}