Fixes issue #1162 and issue #1128; JSON classes now defensively ensure the JavaScript string used as a key is actually a primitive when access the backStore JSO. This was an issue particularly on FF. Includes tests to verify the behavior. Patch by: bobv, scottb Review by: knorton, scottb Found by: colesbury, hoosiemama, joerg.baumann git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1250 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 9874b41..beb8824 100644 --- a/user/src/com/google/gwt/json/client/JSONObject.java +++ b/user/src/com/google/gwt/json/client/JSONObject.java
@@ -25,12 +25,49 @@ * set of {@link com.google.gwt.json.client.JSONValue} objects. */ public class JSONObject extends JSONValue { + + /** + * A sentinel value for {@link #removeBack(JavaScriptObject, String)} to + * return when an entry does not exist. + */ + private static JavaScriptObject UNDEFINED = JavaScriptObject.createObject(); + + private static native void addAllKeysFromJavascriptObject(Set s, + JavaScriptObject javaScriptObject) /*-{ + for(var key in javaScriptObject) { + s.@java.util.Set::add(Ljava/lang/Object;)(key); + } + }-*/; + + private static native JSONValue getFront(JavaScriptObject frontStore, String key) /*-{ + key = String(key); + return Object.prototype.hasOwnProperty.call(frontStore, key) ? frontStore[key] : null; + }-*/; + + private static native void putFront(JavaScriptObject frontStore, String key, + JSONValue jsonValue) /*-{ + frontStore[String(key)] = jsonValue; + }-*/; + + private static native JavaScriptObject removeBack(JavaScriptObject backStore, String key) /*-{ + key = String(key); + if (Object.prototype.hasOwnProperty.call(backStore, key)) { + var x = backStore[key]; + delete backStore[key]; + if (typeof x != 'object') { + x = Object(x); + } + return x; + } + return @com.google.gwt.json.client.JSONObject::UNDEFINED; + }-*/; + private final JavaScriptObject backStore; - private final JavaScriptObject frontStore = createBlankObject(); + private final JavaScriptObject frontStore = JavaScriptObject.createObject(); public JSONObject() { - backStore = createBlankObject(); + backStore = JavaScriptObject.createObject(); } /** @@ -43,60 +80,49 @@ /** * Tests whether or not this JSONObject contains the specified key. * - * We use Object.hasOwnProperty here to verify that a given key is - * specified on this object rather than a superclass (such as standard - * properties defined on Object). + * We use Object.hasOwnProperty here to verify that a given key is specified + * on this object rather than a superclass (such as standard properties + * defined on Object). * * @param key the key to search for * @return <code>true</code> if the JSONObject contains the specified key */ - public native boolean containsKey(String key) /*-{ - return Object.prototype.hasOwnProperty.call( - this.@com.google.gwt.json.client.JSONObject::backStore, key) - || Object.prototype.hasOwnProperty.call( - this.@com.google.gwt.json.client.JSONObject::frontStore, key); - }-*/; + public boolean containsKey(String key) { + return get(key) != null; + } /** * Gets the JSONValue associated with the specified key. * - * We use Object.hasOwnProperty here to verify that a given key is - * specified on this object rather than a superclass (such as standard - * properties defined on Object). + * We use Object.hasOwnProperty here to verify that a given key is specified + * on this object rather than a superclass (such as standard properties + * defined on Object). * * @param key the key to search for * @return if found, the value associated with the specified key, or * <code>null</code> otherwise */ - public native JSONValue get(String key) /*-{ - if (Object.prototype.hasOwnProperty.call( - this.@com.google.gwt.json.client.JSONObject::backStore, key)) - { - var x = this.@com.google.gwt.json.client.JSONObject::backStore[key]; - if (typeof x == 'number' || typeof x == 'string' || typeof x == 'array' - || typeof x == 'boolean') - { - x = Object(x); + public JSONValue get(String key) { + if (key == null) { + return null; + } + JSONValue result = getFront(frontStore, key); + if (result == null) { + JavaScriptObject jso = removeBack(backStore, key); + if (jso != UNDEFINED) { + result = JSONParser.buildValue(jso); + putFront(frontStore, key, result); } - this.@com.google.gwt.json.client.JSONObject::frontStore[key]= - // don't linebreak the next line - @com.google.gwt.json.client.JSONParser::buildValue(Lcom/google/gwt/core/client/JavaScriptObject;)(x); - delete this.@com.google.gwt.json.client.JSONObject::backStore[key]; } - if (Object.prototype.hasOwnProperty.call( - this.@com.google.gwt.json.client.JSONObject::frontStore, key)) - { - return this.@com.google.gwt.json.client.JSONObject::frontStore[key]; - } - return null; - }-*/; + return result; + } /** * Returns <code>this</code>, as this is a JSONObject. */ public JSONObject isObject() { return this; - } + }; /** * Returns keys for which this JSONObject has associations. @@ -115,16 +141,19 @@ * specified key already has an associated value, it is overwritten. * * @param key the key to associate with the specified value - * @param jsonValue the value to assoociate with this key + * @param jsonValue the value to associate with this key * @return if one existed, the previous value associated with the key, or * <code>null</code> otherwise + * @throws NullPointerException if key is <code>null</code> */ - public native JSONValue put(String key, JSONValue jsonValue) /*-{ - var out = // can't break next line - this.@com.google.gwt.json.client.JSONObject::get(Ljava/lang/String;)(key); - this.@com.google.gwt.json.client.JSONObject::frontStore[key] = jsonValue; - return out; - }-*/; + public JSONValue put(String key, JSONValue jsonValue) { + if (key == null) { + throw new NullPointerException(); + } + JSONValue previous = get(key); + putFront(frontStore, key, jsonValue); + return previous; + } /** * Determines the number of keys on this object. @@ -164,15 +193,4 @@ out.push("}") return out.join(""); }-*/; - - private native void addAllKeysFromJavascriptObject(Set s, - JavaScriptObject javaScriptObject) /*-{ - for(var key in javaScriptObject) { - s.@java.util.Set::add(Ljava/lang/Object;)(key); - } - }-*/; - - private native JavaScriptObject createBlankObject() /*-{ - return {}; - }-*/; }
diff --git a/user/test/com/google/gwt/json/client/JSONTest.java b/user/test/com/google/gwt/json/client/JSONTest.java index 0da0d49..29d5532 100644 --- a/user/test/com/google/gwt/json/client/JSONTest.java +++ b/user/test/com/google/gwt/json/client/JSONTest.java
@@ -301,16 +301,16 @@ JSONObject j2 = new JSONObject(); j2.put("test1", new JSONString("")); - JSONObject j2_2 = new JSONObject(); - j2_2.put("test1_2", new JSONString("")); - j2.put("j2_2", j2_2); + JSONObject j22 = new JSONObject(); + j22.put("test12", new JSONString("")); + j2.put("j22", j22); JSONObject j3 = new JSONObject(); j3.put("j1", j1); j3.put("j2", j2); assertEquals( - "{\"j1\":{\"test1\":\"\"}, \"j2\":{\"test1\":\"\", \"j2_2\":{\"test1_2\":\"\"}}}", + "{\"j1\":{\"test1\":\"\"}, \"j2\":{\"test1\":\"\", \"j22\":{\"test12\":\"\"}}}", j3.toString()); } @@ -328,6 +328,17 @@ checkRoundTripJsonText("\"hel\\\\\\\"lo\"", "hel\\\"lo"); } + public void testStringTypes() { + JSONObject object = JSONParser.parse("{\"a\":\"b\",\"null\":\"foo\"}").isObject(); + assertNotNull(object); + + 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()); + assertNull(object.get(null)); + } + public void testWidget() { JSONObject v = (JSONObject) JSONParser.parse(widgetTest); JSONObject widget = (JSONObject) v.get("widget"); @@ -388,4 +399,11 @@ return newArray; } + private native String stringAsObject(String str) /*-{ + return new String(str); + }-*/; + + private native String stringAsPrimitive(String str) /*-{ + return String(str); + }-*/; }