Fixes issue 2008.  Corrects JSONObject's toString to properly escape
backslashes and quotes.

Issue: 2008
Patch by: fredsa, jat
Review by: scottb



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1963 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/json/client/JSONObject.java b/user/src/com/google/gwt/json/client/JSONObject.java
index b1fe522..84043e7 100644
--- a/user/src/com/google/gwt/json/client/JSONObject.java
+++ b/user/src/com/google/gwt/json/client/JSONObject.java
@@ -185,9 +185,8 @@
       var subObj = 
         (this.@com.google.gwt.json.client.JSONObject::frontStore[key]).
           @com.google.gwt.json.client.JSONValue::toString()();
-      out.push("\"");
-      out.push(key);
-      out.push("\":");
+      out.push(@com.google.gwt.json.client.JSONString::escapeValue(Ljava/lang/String;)(key));
+      out.push(":");
       out.push(subObj);
     }
     out.push("}")
diff --git a/user/src/com/google/gwt/json/client/JSONString.java b/user/src/com/google/gwt/json/client/JSONString.java
index fbd824e..73bce23 100644
--- a/user/src/com/google/gwt/json/client/JSONString.java
+++ b/user/src/com/google/gwt/json/client/JSONString.java
@@ -29,6 +29,13 @@
     return (lookedUp == null) ? c : lookedUp;
   }-*/;
 
+  static native String escapeValue(String toEscape) /*-{
+    var s = toEscape.replace(/[\x00-\x1F"\\]/g, function(x) {
+      return @com.google.gwt.json.client.JSONString::escapeChar(Ljava/lang/String;)(x);
+    });
+    return "\"" + s + "\"";
+  }-*/;
+
   private static native JavaScriptObject initEscapeTable() /*-{
     var out = [
       "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005",
@@ -80,11 +87,4 @@
   public String toString() {
     return escapeValue(value);
   }
-
-  private native String escapeValue(String toEscape) /*-{
-    var s = toEscape.replace(/[\x00-\x1F"\\]/g, function(x) {
-      return @com.google.gwt.json.client.JSONString::escapeChar(Ljava/lang/String;)(x);
-    });
-    return "\"" + s + "\"";
-  }-*/;
 }
diff --git a/user/test/com/google/gwt/json/client/JSONTest.java b/user/test/com/google/gwt/json/client/JSONTest.java
index e5a9feb..d946328 100644
--- a/user/test/com/google/gwt/json/client/JSONTest.java
+++ b/user/test/com/google/gwt/json/client/JSONTest.java
@@ -17,8 +17,10 @@
 
 import com.google.gwt.junit.client.GWTTestCase;
 
+import java.util.Set;
+
 /**
- * TODO: document me.
+ * Test case for JSONValue and friends.
  */
 public class JSONTest extends GWTTestCase {
   static final String menuTest = "{\"menu\": {\n" + "  \"id\": \"file\",\n"
@@ -43,6 +45,54 @@
       + "        \"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n"
       + "    }\n" + "}}    \n" + "";
 
+  private static void assertJSONArrayEquals(JSONArray expected, JSONArray actual) {
+    assertEquals(expected.size(), actual.size());
+    for (int i = 0; i < expected.size(); ++i) {
+      assertJSONValueEquals(expected.get(i), actual.get(i));
+    }
+  }
+
+  private static void assertJSONObjectEquals(JSONObject expected,
+      JSONObject actual) {
+    Set<String> actKeys = actual.keySet();
+    for (String key : expected.keySet()) {
+      actKeys.remove(key);
+      assertTrue(actual.containsKey(key));
+      JSONValue expValue = expected.get(key);
+      JSONValue actValue = actual.get(key);
+      assertJSONValueEquals(expValue, actValue);
+    }
+    assertEquals(0, actKeys.size());
+  }
+
+  private static void assertJSONValueEquals(JSONValue expected, JSONValue actual) {
+    if (expected.isArray() != null) {
+      JSONArray expArray = expected.isArray();
+      JSONArray actArray = actual.isArray();
+      assertJSONArrayEquals(expArray, actArray);
+    } else if (expected.isBoolean() != null) {
+      JSONBoolean expBool = expected.isBoolean();
+      JSONBoolean actBool = actual.isBoolean();
+      assertEquals(expBool.booleanValue(), actBool.booleanValue());
+    } else if (expected.isNull() != null) {
+      assertNotNull(actual.isNull());
+    } else if (expected.isNumber() != null) {
+      JSONNumber expNum = expected.isNumber();
+      JSONNumber actNum = actual.isNumber();
+      assertEquals(expNum.getValue(), actNum.getValue());
+    } else if (expected.isObject() != null) {
+      JSONObject expObj = expected.isObject();
+      JSONObject actObj = actual.isObject();
+      assertJSONObjectEquals(expObj, actObj);
+    } else if (expected.isString() != null) {
+      JSONString expStr = expected.isString();
+      JSONString actStr = actual.isString();
+      assertEquals(expStr.stringValue(), actStr.stringValue());
+    } else {
+      fail("Unknown JSONValue " + expected);
+    }
+  }
+
   /**
    * Returns the module name for GWT unit test running.
    */
@@ -294,6 +344,19 @@
             "hello"));
   }
 
+  public void testRoundTripEscaping() {
+    JSONObject obj = new JSONObject();
+    obj.put("a", new JSONNumber(42));
+    obj.put("\\", new JSONNumber(43));
+    obj.put("\"", new JSONNumber(44));
+
+    String toString = obj.toString();
+    assertEquals("{\"a\":42.0, \"\\\\\":43.0, \"\\\"\":44.0}", toString.trim());
+    JSONValue parseResponse = JSONParser.parse(toString);
+    JSONObject obj2 = parseResponse.isObject();
+    assertJSONObjectEquals(obj, obj2);
+  }
+
   public void testSimpleNested() {
     JSONObject j1 = new JSONObject();
     j1.put("test1", new JSONString(""));
@@ -332,10 +395,13 @@
     JSONObject object = JSONParser.parse("{\"a\":\"b\",\"null\":\"foo\"}").isObject();
     assertNotNull(object);
 
-    assertEquals("b", object.get(stringAsPrimitive("a")).isString().stringValue());
+    assertEquals("b",
+        object.get(stringAsPrimitive("a")).isString().stringValue());
     assertEquals("b", object.get(stringAsObject("a")).isString().stringValue());
-    assertEquals("foo", object.get(stringAsPrimitive("null")).isString().stringValue());
-    assertEquals("foo", object.get(stringAsObject("null")).isString().stringValue());
+    assertEquals("foo",
+        object.get(stringAsPrimitive("null")).isString().stringValue());
+    assertEquals("foo",
+        object.get(stringAsObject("null")).isString().stringValue());
     assertNull(object.get(null));
   }