Index: xstream/src/test/com/thoughtworks/xstream/io/json/JsonHierarchicalStreamDriverTest.java =================================================================== --- xstream/src/test/com/thoughtworks/xstream/io/json/JsonHierarchicalStreamDriverTest.java (revision 1584) +++ xstream/src/test/com/thoughtworks/xstream/io/json/JsonHierarchicalStreamDriverTest.java (working copy) @@ -74,7 +74,7 @@ public void testCanMarshalSimpleTypes() { - String expected = ("{'innerMessage': {\n" + String expected = ("{\n" + " 'long1': 5,\n" + " 'long2': 42,\n" + " 'greeting': 'hello',\n" @@ -91,7 +91,7 @@ + " 'bool': false,\n" + " 'char1': '\\u0000'\n" + " }\n" - + "}}").replace('\'', '"'); + + "}").replace('\'', '"'); xstream.alias("innerMessage", Message.class); @@ -131,7 +131,7 @@ } String expectedMenuStart = "" - + "{'menu': {\n" + + "{\n" + " 'id': 'file',\n" + " 'value': 'File:',\n" + " 'popup': {\n" @@ -151,7 +151,7 @@ + " 'value': 'Close',\n" + " 'onclick': 'CloseDoc()'\n" + " }"; - String expectedMenuEnd = "" + " ]\n" + " }\n" + "}}"; + String expectedMenuEnd = "" + " ]\n" + " }\n" + "}"; String expected = (expectedMenuStart + "\n" + expectedNew @@ -264,7 +264,7 @@ // This also from http://www.expected.org/example.html - String expected = ("{'widget': {\n" + String expected = ("{\n" + " 'debug': 'on',\n" + " 'window': {\n" + " 'title': 'Sample Konfabulator Widget',\n" @@ -289,7 +289,7 @@ + " 'alignment': 'center',\n" + " 'onMouseUp': 'sun1.opacity = (sun1.opacity / 100) * 90;'\n" + " }\n" - + "}}").replace('\'', '"'); + + "}").replace('\'', '"'); xstream.alias("widget", Widget.class); xstream.alias("window", Window.class); @@ -339,12 +339,12 @@ boolean isHeadless = Boolean.valueOf(System.getProperty("java.awt.headless", "false")).booleanValue(); if (!isHeadless || JVM.is15()) { Color color = Color.black; - String expected = ("{'awt-color': {\n" + String expected = ("{\n" + " 'red': 0,\n" + " 'green': 0,\n" + " 'blue': 0,\n" + " 'alpha': 255\n" - + "}}").replace('\'', '"'); + + "}").replace('\'', '"'); assertEquals(expected, xstream.toXML(color)); } } @@ -353,7 +353,7 @@ String[] strings = new String[]{ "last\"", "\"first", "\"between\"", "around \"\" it", "back\\slash",}; String expected = ("" - + "{#string-array#: [\n" + + "{[\n" + " #last\\\"#,\n" + " #\\\"first#,\n" + " #\\\"between\\\"#,\n" @@ -371,10 +371,10 @@ xstream.alias("innerMessage", Msg.class); String expected = ("" - + "{'innerMessage': {\n" + + "{\n" + " 'greeting': 'hello',\n" + " 'innerMessage': {}\n" - + "}}").replace('\'', '"'); + + "}").replace('\'', '"'); assertEquals(expected, xstream.toXML(message)); } @@ -390,7 +390,7 @@ public void testCanMarshalElementWithEmptyArray() { xstream.alias("element", ElementWithEmptyArray.class); - String expected = ("" + "{'element': {\n" + " 'array': [\n" + " ]\n" + "}}").replace( + String expected = ("" + "{\n" + " 'array': [\n" + " ]\n" + "}").replace( '\'', '"'); assertEquals(expected, xstream.toXML(new ElementWithEmptyArray())); } @@ -419,7 +419,7 @@ int idx2 = actual.indexOf("two"); String expected = ("" - + "{'map': [\n" + + "{[\n" + ((idx1 < idx2 ? entry1 : entry2) + ",\n") + ((idx1 < idx2 ? entry2 : entry1) + "\n") // no comma + "]}").replace('\'', '"'); @@ -446,7 +446,7 @@ int idx2 = actual.indexOf("two"); String expected = ("" - + "{'properties': [\n" + + "{[\n" + ((idx1 < idx2 ? entry1 : entry2) + ",\n") + ((idx1 < idx2 ? entry2 : entry1) + "\n") // no comma + "]}").replace('\'', '"'); @@ -478,12 +478,12 @@ int idx2 = actual.indexOf("two"); String expected = ("" - + "{'holder': {\n" + + "{\n" + " 'map': [\n" + ((idx1 < idx2 ? entry1 : entry2) + ",\n") + ((idx1 < idx2 ? entry2 : entry1) + "\n") + " ]\n" // no comma - + "}}").replace('\'', '"'); + + "}").replace('\'', '"'); assertEquals(expected, actual); } @@ -494,12 +494,12 @@ public void testIgnoresAttributeForCollectionMember() { xstream.alias("keeper", CollectionKeeper.class); String expected = ("" // - + "{'keeper': {\n" + + "{\n" + " 'coll': [\n" + " 'one',\n" + " 'two'\n" + " ]\n" - + "}}").replace('\'', '"'); + + "}").replace('\'', '"'); final CollectionKeeper holder = new CollectionKeeper(); holder.coll.add("one"); @@ -513,11 +513,11 @@ xstream.useAttributeFor("width", int.class); xstream.useAttributeFor("height", int.class); String expected = ("" - + "{'window': {\n" + + "{\n" + " '@width': '500',\n" + " '@height': '500',\n" + " 'title': 'JUnit'\n" - + "}}").replace('\'', '"'); + + "}").replace('\'', '"'); final Window window = new Window(); window.title = "JUnit"; @@ -535,7 +535,7 @@ public void testCanWriteEmbeddedCalendar() { xstream.alias("person", Person.class); String expected = ("" - + "{'list': [\n" + + "{[\n" + " {\n" + " 'firstName': 'Joe',\n" + " 'lastName': 'Walnes',\n" @@ -566,10 +566,22 @@ } public void testDoesEscapeValuesAccordingRfc4627() { - String expected = "{'string': '\\u0000\\u0001\\u001f \uffee'}".replace('\'', '"'); + String expected = "{'\\u0000\\u0001\\u001f \uffee'}".replace('\'', '"'); assertEquals(expected, xstream.toXML("\u0000\u0001\u001f\u0020\uffee")); } + public void testSimpleInt() { + assertEquals("{123}", xstream.toXML(123)); + } + + public void testBracesAndSquareBracketsAreNotEscaped() { + assertEquals("{\"..{}[]..\"}", xstream.toXML("..{}[]..")); + } + + public void testQuoteIsEscaped() { + assertEquals("{\"..\\\"..\"}", xstream.toXML("..\"..")); + } + static class SingleValue { long l; URL url; @@ -578,10 +590,10 @@ public void testSupportsAllConvertersWithASingleValue() throws MalformedURLException { xstream.alias("sv", SingleValue.class); String expected = ("" - + "{'sv': {\n" + + "{\n" + " 'l': 4711,\n" + " 'url': 'http://localhost:8888'\n" - + "}}").replace('\'', '"'); + + "}").replace('\'', '"'); SingleValue value = new SingleValue(); value.l = 4711; @@ -606,7 +618,7 @@ sa.original = new Original("hello world"); String expected = ("" - + "{'sa': {\n" + + "{\n" + " 'name': 'joe',\n" + " 'charSeq': {\n" + " '@class': 'string',\n" @@ -616,7 +628,7 @@ + " '@resolves-to': 'replaced',\n" + " 'replacedValue': 'HELLO WORLD'\n" + " }\n" - + "}}").replace('\'', '"'); + + "}").replace('\'', '"'); assertEquals(expected, xstream.toXML(sa)); } Index: xstream/src/java/com/thoughtworks/xstream/io/json/JsonHierarchicalStreamWriter.java =================================================================== --- xstream/src/java/com/thoughtworks/xstream/io/json/JsonHierarchicalStreamWriter.java (revision 1584) +++ xstream/src/java/com/thoughtworks/xstream/io/json/JsonHierarchicalStreamWriter.java (working copy) @@ -40,6 +40,7 @@ private boolean readyForNewLine; private boolean tagIsEmpty; private String newLine; + private boolean firstChar = true; public JsonHierarchicalStreamWriter(Writer writer, char[] lineIndenter, String newLine) { this.writer = new QuickWriter(writer); @@ -73,11 +74,11 @@ public void startNode(String name, Class clazz) { Node currNode = (Node)elementStack.peek(); - if (currNode == null) { - writer.write("{"); + if (currNode == null && (depth > 0 || isCollection(clazz))) { + write("{"); } if (currNode != null && currNode.fieldAlready) { - writer.write(","); + write(","); readyForNewLine = true; } tagIsEmpty = false; @@ -86,16 +87,18 @@ || currNode.clazz == null || (currNode.clazz != null && !currNode.isCollection)) { if (currNode != null && !currNode.fieldAlready) { - writer.write("{"); + write("{"); readyForNewLine = true; finishTag(); } - writer.write("\""); - writer.write(name); - writer.write("\": "); + if (depth > 0) { + write("\""); + write(name); + write("\": "); + } } if (isCollection(clazz)) { - writer.write("["); + write("["); readyForNewLine = true; } if (currNode != null) { @@ -106,6 +109,30 @@ tagIsEmpty = true; } + private void write(String string) { + if (firstChar && string.charAt(0) != '{') { + writer.write('{'); + } + firstChar = false; + writer.write(string); + } + + private void write(char chr) { + if (firstChar && chr != '{') { + writer.write('{'); + } + firstChar = false; + writer.write(chr); + } + + private void write(char[] chars) { + if (firstChar && chars[0] != '{') { + writer.write('{'); + } + firstChar = false; + writer.write(chars); + } + public class Node { public final String name; public final Class clazz; @@ -156,7 +183,7 @@ private void writeText(String text, Class clazz) { if (needsQuotes(clazz)) { - writer.write("\""); + write("\""); } if ((clazz == Character.class || clazz == Character.TYPE) && "".equals(text)) { text = "\0"; @@ -167,24 +194,24 @@ char c = text.charAt(i); switch (c) { case '"': - this.writer.write("\\\""); + write("\\\""); break; case '\\': - this.writer.write("\\\\"); + write("\\\\"); break; default: if (c > 0x1f) { - this.writer.write(c); + write(c); } else { - this.writer.write("\\u"); + write("\\u"); String hex = "000" + Integer.toHexString(c); - this.writer.write(hex.substring(hex.length() - 4)); + write(hex.substring(hex.length() - 4)); } } } if (needsQuotes(clazz)) { - writer.write("\""); + write("\""); } } @@ -208,21 +235,21 @@ readyForNewLine = true; } finishTag(); - writer.write("]"); + write("]"); } else if (tagIsEmpty) { readyForNewLine = false; - writer.write("{}"); + write("{}"); finishTag(); } else { finishTag(); - if (node.fieldAlready) { - writer.write("}"); + if (node.fieldAlready && depth > 0) { + write("}"); } } readyForNewLine = true; if (depth == 0) { - writer.write("}"); - writer.flush(); + write("}"); + flush(); } } @@ -235,9 +262,9 @@ } protected void endOfLine() { - writer.write(newLine); + write(newLine); for (int i = 0; i < depth; i++ ) { - writer.write(lineIndenter); + write(lineIndenter); } }