Permit multiple top-level values in JsonWriter in lenient mode. Also fix some cases where we don't throw the right thing on a closed JsonWriter.

I'd prefer to not support multiple top-level values, but we support it in JsonReader and it's easier to be consistent. Kevin Hayen's patch pointed me in the right direction here, but I needed to do more work to cover some of the edge cases.

Fixes issue 397.
This commit is contained in:
Jesse Wilson 2012-02-12 20:42:16 +00:00
parent 5c978948a0
commit 2c8bec27d4
2 changed files with 122 additions and 8 deletions

View File

@ -314,7 +314,11 @@ public class JsonWriter implements Closeable {
* Returns the value on the top of the stack. * Returns the value on the top of the stack.
*/ */
private JsonScope peek() { private JsonScope peek() {
return stack.get(stack.size() - 1); int size = stack.size();
if (size == 0) {
throw new IllegalStateException("JsonWriter is closed.");
}
return stack.get(size - 1);
} }
/** /**
@ -337,6 +341,9 @@ public class JsonWriter implements Closeable {
if (deferredName != null) { if (deferredName != null) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
if (stack.isEmpty()) {
throw new IllegalStateException("JsonWriter is closed.");
}
deferredName = name; deferredName = name;
return this; return this;
} }
@ -453,6 +460,9 @@ public class JsonWriter implements Closeable {
* and flushes that writer. * and flushes that writer.
*/ */
public void flush() throws IOException { public void flush() throws IOException {
if (stack.isEmpty()) {
throw new IllegalStateException("JsonWriter is closed.");
}
out.flush(); out.flush();
} }
@ -464,9 +474,11 @@ public class JsonWriter implements Closeable {
public void close() throws IOException { public void close() throws IOException {
out.close(); out.close();
if (peek() != JsonScope.NONEMPTY_DOCUMENT) { int size = stack.size();
if (size > 1 || size == 1 && stack.get(size - 1) != JsonScope.NONEMPTY_DOCUMENT) {
throw new IOException("Incomplete document"); throw new IOException("Incomplete document");
} }
stack.clear();
} }
private void string(String value) throws IOException { private void string(String value) throws IOException {
@ -574,8 +586,15 @@ public class JsonWriter implements Closeable {
* @param root true if the value is a new array or object, the two values * @param root true if the value is a new array or object, the two values
* permitted as top-level elements. * permitted as top-level elements.
*/ */
@SuppressWarnings("fallthrough")
private void beforeValue(boolean root) throws IOException { private void beforeValue(boolean root) throws IOException {
switch (peek()) { switch (peek()) {
case NONEMPTY_DOCUMENT:
if (!lenient) {
throw new IllegalStateException(
"JSON must have only one top-level value.");
}
// fall-through
case EMPTY_DOCUMENT: // first in document case EMPTY_DOCUMENT: // first in document
if (!lenient && !root) { if (!lenient && !root) {
throw new IllegalStateException( throw new IllegalStateException(
@ -599,10 +618,6 @@ public class JsonWriter implements Closeable {
replaceTop(JsonScope.NONEMPTY_OBJECT); replaceTop(JsonScope.NONEMPTY_OBJECT);
break; break;
case NONEMPTY_DOCUMENT:
throw new IllegalStateException(
"JSON must have only one top-level value.");
default: default:
throw new IllegalStateException("Nesting problem: " + stack); throw new IllegalStateException("Nesting problem: " + stack);
} }

View File

@ -16,12 +16,11 @@
package com.google.gson.stream; package com.google.gson.stream;
import junit.framework.TestCase;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import junit.framework.TestCase;
public final class JsonWriterTest extends TestCase { public final class JsonWriterTest extends TestCase {
@ -464,4 +463,104 @@ public final class JsonWriterTest extends TestCase {
+ "]"; + "]";
assertEquals(expected, stringWriter.toString()); assertEquals(expected, stringWriter.toString());
} }
public void testLenientWriterPermitsMultipleTopLevelValues() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter);
writer.setLenient(true);
writer.beginArray();
writer.endArray();
writer.beginArray();
writer.endArray();
writer.close();
assertEquals("[][]", stringWriter.toString());
}
public void testStrictWriterDoesNotPermitMultipleTopLevelValues() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray();
writer.endArray();
try {
writer.beginArray();
fail();
} catch (IllegalStateException expected) {
}
}
public void testClosedWriterThrowsOnStructure() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray();
writer.endArray();
writer.close();
try {
writer.beginArray();
fail();
} catch (IllegalStateException expected) {
}
try {
writer.endArray();
fail();
} catch (IllegalStateException expected) {
}
try {
writer.beginObject();
fail();
} catch (IllegalStateException expected) {
}
try {
writer.endObject();
fail();
} catch (IllegalStateException expected) {
}
}
public void testClosedWriterThrowsOnName() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray();
writer.endArray();
writer.close();
try {
writer.name("a");
fail();
} catch (IllegalStateException expected) {
}
}
public void testClosedWriterThrowsOnValue() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray();
writer.endArray();
writer.close();
try {
writer.value("a");
fail();
} catch (IllegalStateException expected) {
}
}
public void testClosedWriterThrowsOnFlush() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray();
writer.endArray();
writer.close();
try {
writer.flush();
fail();
} catch (IllegalStateException expected) {
}
}
public void testWriterCloseIsIdempotent() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray();
writer.endArray();
writer.close();
writer.close();
}
} }