Rewrote JSON library.
Review by: bruce
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2002 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/json/client/JSONArray.java b/user/src/com/google/gwt/json/client/JSONArray.java
index b9d24a2..97024ba 100644
--- a/user/src/com/google/gwt/json/client/JSONArray.java
+++ b/user/src/com/google/gwt/json/client/JSONArray.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * Copyright 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
@@ -21,17 +21,22 @@
* Represents an array of {@link com.google.gwt.json.client.JSONValue} objects.
*/
public class JSONArray extends JSONValue {
+
+ /**
+ * Called from {@link #getUnwrapper()}.
+ */
+ @SuppressWarnings("unused")
+ private static JavaScriptObject unwrap(JSONArray value) {
+ return value.jsArray;
+ }
- final JavaScriptObject javascriptArray;
-
- final JavaScriptObject wrappedArray;
+ private final JavaScriptObject jsArray;
/**
* Creates an empty JSONArray.
*/
public JSONArray() {
- javascriptArray = createArray();
- wrappedArray = createArray();
+ jsArray = JavaScriptObject.createArray();
}
/**
@@ -41,8 +46,19 @@
* @param arr a JavaScript array
*/
public JSONArray(JavaScriptObject arr) {
- javascriptArray = arr;
- wrappedArray = createArray();
+ jsArray = arr;
+ }
+
+ /**
+ * Returns <code>true</code> if <code>other</code> is a {@link JSONArray}
+ * wrapping the same underlying object.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof JSONArray)) {
+ return false;
+ }
+ return jsArray.equals(((JSONArray) other).jsArray);
}
/**
@@ -52,22 +68,22 @@
* @return the value at this index, or <code>null</code> if this index is
* empty
*/
- public JSONValue get(int index) throws JSONException {
- if (wrappedTest(index)) {
- return wrappedGet(index);
- }
- JSONValue wrapped = null;
- if (rawTest(index)) {
- Object o = rawGet(index);
- if (o instanceof String) {
- wrapped = new JSONString((String) o);
- } else {
- wrapped = JSONParser.buildValue((JavaScriptObject) o);
- }
- rawSet(index, null);
- }
- wrappedSet(index, wrapped);
- return wrapped;
+ public native JSONValue get(int index) /*-{
+ var v = this.@com.google.gwt.json.client.JSONArray::jsArray[index];
+ var func = @com.google.gwt.json.client.JSONParser::typeMap[typeof v];
+ return func ? func(v) : @com.google.gwt.json.client.JSONParser::throwUnknownTypeException(Ljava/lang/String;)(typeof v);
+ }-*/;
+
+ /**
+ * Returns the underlying JavaScript array that this object wraps.
+ */
+ public JavaScriptObject getJavaScriptObject() {
+ return jsArray;
+ }
+
+ @Override
+ public int hashCode() {
+ return jsArray.hashCode();
}
/**
@@ -82,15 +98,14 @@
* Sets the specified index to the given value.
*
* @param index the index to set
- * @param jsonValue the value to set
+ * @param value the value to set
* @return the previous value at this index, or <code>null</code> if this
* index was empty
*/
- public JSONValue set(int index, JSONValue jsonValue) {
- JSONValue out = get(index);
- wrappedSet(index, jsonValue);
- rawSet(index, null);
- return out;
+ public JSONValue set(int index, JSONValue value) {
+ JSONValue previous = get(index);
+ set0(index, value);
+ return previous;
}
/**
@@ -99,7 +114,7 @@
* @return size of this array
*/
public native int size() /*-{
- return this.@com.google.gwt.json.client.JSONArray::javascriptArray.length;
+ return this.@com.google.gwt.json.client.JSONArray::jsArray.length;
}-*/;
/**
@@ -108,52 +123,31 @@
* large.
*/
@Override
- public String toString() throws JSONException {
+ public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("[");
for (int i = 0, c = size(); i < c; i++) {
- JSONValue value = get(i);
- sb.append(value.toString());
-
- if (i < c - 1) {
+ if (i > 0) {
sb.append(",");
}
+ sb.append(get(i));
}
sb.append("]");
return sb.toString();
}
- private native JavaScriptObject createArray() /*-{
- return [];
+ @Override
+ native JavaScriptObject getUnwrapper() /*-{
+ return @com.google.gwt.json.client.JSONArray::unwrap(Lcom/google/gwt/json/client/JSONArray;);
}-*/;
- private native Object rawGet(int index) /*-{
- var x = this.@com.google.gwt.json.client.JSONArray::javascriptArray[index];
- if (typeof x == 'number' || typeof x == 'array' || typeof x == 'boolean') {
- x = (Object(x));
+ private native void set0(int index, JSONValue value) /*-{
+ if (value === null) {
+ value = undefined;
+ } else {
+ var func = value.@com.google.gwt.json.client.JSONValue::getUnwrapper()();
+ value = func(value);
}
- return x;
- }-*/;
-
- private native void rawSet(int index, Object value) /*-{
- this.@com.google.gwt.json.client.JSONArray::javascriptArray[index] = value;
- }-*/;
-
- private native boolean rawTest(int index) /*-{
- var x = this.@com.google.gwt.json.client.JSONArray::javascriptArray[index];
- return x !== undefined;
- }-*/;
-
- private native JSONValue wrappedGet(int index) /*-{
- return this.@com.google.gwt.json.client.JSONArray::wrappedArray[index];
- }-*/;
-
- private native void wrappedSet(int index, JSONValue jsonValue) /*-{
- this.@com.google.gwt.json.client.JSONArray::wrappedArray[index] = jsonValue;
- }-*/;
-
- private native boolean wrappedTest(int index) /*-{
- var x = this.@com.google.gwt.json.client.JSONArray::wrappedArray[index];
- return x !== undefined;
+ this.@com.google.gwt.json.client.JSONArray::jsArray[index] = value;
}-*/;
}
diff --git a/user/src/com/google/gwt/json/client/JSONBoolean.java b/user/src/com/google/gwt/json/client/JSONBoolean.java
index c4ba714..d92b8f1 100644
--- a/user/src/com/google/gwt/json/client/JSONBoolean.java
+++ b/user/src/com/google/gwt/json/client/JSONBoolean.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * Copyright 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
@@ -15,6 +15,8 @@
*/
package com.google.gwt.json.client;
+import com.google.gwt.core.client.JavaScriptObject;
+
/**
* Represents a JSON boolean value.
*/
@@ -40,6 +42,14 @@
}
}
+ /**
+ * Called from {@link #getUnwrapper()}.
+ */
+ @SuppressWarnings("unused")
+ private static boolean unwrap(JSONBoolean value) {
+ return value.value;
+ }
+
private final boolean value;
/*
@@ -72,4 +82,9 @@
public String toString() {
return Boolean.toString(value);
}
+
+ @Override
+ native JavaScriptObject getUnwrapper() /*-{
+ return @com.google.gwt.json.client.JSONBoolean::unwrap(Lcom/google/gwt/json/client/JSONBoolean;);
+ }-*/;
}
diff --git a/user/src/com/google/gwt/json/client/JSONNull.java b/user/src/com/google/gwt/json/client/JSONNull.java
index 7717099..9873f8a 100644
--- a/user/src/com/google/gwt/json/client/JSONNull.java
+++ b/user/src/com/google/gwt/json/client/JSONNull.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * Copyright 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
@@ -15,6 +15,8 @@
*/
package com.google.gwt.json.client;
+import com.google.gwt.core.client.JavaScriptObject;
+
/**
* Represents the JSON <code>null</code> value.
*/
@@ -30,6 +32,14 @@
}
/**
+ * Called from {@link #getUnwrapper()}.
+ */
+ @SuppressWarnings("unused")
+ private static JavaScriptObject unwrap() {
+ return null;
+ }
+
+ /**
* There should only be one null value.
*/
private JSONNull() {
@@ -51,4 +61,8 @@
return "null";
}
+ native JavaScriptObject getUnwrapper() /*-{
+ return @com.google.gwt.json.client.JSONNull::unwrap();
+ }-*/;
+
}
diff --git a/user/src/com/google/gwt/json/client/JSONNumber.java b/user/src/com/google/gwt/json/client/JSONNumber.java
index 6cb3640..2bd6682 100644
--- a/user/src/com/google/gwt/json/client/JSONNumber.java
+++ b/user/src/com/google/gwt/json/client/JSONNumber.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * Copyright 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
@@ -15,11 +15,21 @@
*/
package com.google.gwt.json.client;
+import com.google.gwt.core.client.JavaScriptObject;
+
/**
* Represents a JSON number. Numbers are represented by <code>double</code>s.
*/
public class JSONNumber extends JSONValue {
+ /**
+ * Called from {@link #getUnwrapper()}.
+ */
+ @SuppressWarnings("unused")
+ private static double unwrap(JSONNumber value) {
+ return value.value;
+ }
+
private double value;
/**
@@ -30,12 +40,36 @@
}
/**
- * Gets the double value that this JSONNumber represents.
+ * Gets the double value this JSONNumber represents.
*/
+ public double doubleValue() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof JSONNumber)) {
+ return false;
+ }
+ return value == ((JSONNumber) other).value;
+ }
+
+ /**
+ * Gets the double value this JSONNumber represents.
+ *
+ * @deprecated use {@link #doubleValue()}
+ */
+ @Deprecated
public double getValue() {
return value;
}
+ @Override
+ public int hashCode() {
+ // Just use the underlying double's hashCode.
+ return Double.valueOf(value).hashCode();
+ }
+
/**
* Returns <code>this</code>, as this is a JSONNumber.
*/
@@ -52,4 +86,9 @@
// Use JavaScript conversion so that integral values print as integers.
return this.@com.google.gwt.json.client.JSONNumber::value + "";
}-*/;
+
+ @Override
+ native JavaScriptObject getUnwrapper() /*-{
+ return @com.google.gwt.json.client.JSONNumber::unwrap(Lcom/google/gwt/json/client/JSONNumber;);
+ }-*/;
}
diff --git a/user/src/com/google/gwt/json/client/JSONObject.java b/user/src/com/google/gwt/json/client/JSONObject.java
index 84043e7..e94b0b7 100644
--- a/user/src/com/google/gwt/json/client/JSONObject.java
+++ b/user/src/com/google/gwt/json/client/JSONObject.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * Copyright 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
@@ -17,102 +17,85 @@
import com.google.gwt.core.client.JavaScriptObject;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
- * Represents a JSON object. A JSON object is a map of string-based keys onto a
- * set of {@link com.google.gwt.json.client.JSONValue} objects.
+ * Represents a JSON object. A JSON object consists of a set of properties.
*/
public class JSONObject extends JSONValue {
- private static native void addAllKeysFromJavascriptObject(Set<String> s,
- JavaScriptObject javaScriptObject) /*-{
- for(var key in javaScriptObject) {
- s.@java.util.Set::add(Ljava/lang/Object;)(key);
- }
- }-*/;
+ /**
+ * Called from {@link #getUnwrapper()}.
+ */
+ @SuppressWarnings("unused")
+ private static JavaScriptObject unwrap(JSONObject value) {
+ return value.jsObject;
+ }
- private static native boolean containsBack(JavaScriptObject backStore, String key) /*-{
- key = String(key);
- return Object.prototype.hasOwnProperty.call(backStore, 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 Object removeBack(JavaScriptObject backStore, String key) /*-{
- key = String(key);
- var result = backStore[key];
- delete backStore[key];
- if (typeof result != 'object') {
- result = Object(result);
- }
- return result;
- }-*/;
-
- private final JavaScriptObject backStore;
-
- private final JavaScriptObject frontStore = JavaScriptObject.createObject();
+ private final JavaScriptObject jsObject;
public JSONObject() {
- backStore = JavaScriptObject.createObject();
+ jsObject = JavaScriptObject.createObject();
}
/**
* Creates a new JSONObject from the supplied JavaScript value.
*/
public JSONObject(JavaScriptObject jsValue) {
- backStore = jsValue;
+ jsObject = jsValue;
}
/**
- * Tests whether or not this JSONObject contains the specified key.
+ * Tests whether or not this JSONObject contains the specified property.
*
- * 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
+ * @param key the property to search for
+ * @return <code>true</code> if the JSONObject contains the specified property
*/
- public boolean containsKey(String key) {
- return get(key) != null;
+ public native boolean containsKey(String key) /*-{
+ return this.@com.google.gwt.json.client.JSONObject::jsObject[key] !== undefined;
+ }-*/;
+
+ /**
+ * Returns <code>true</code> if <code>other</code> is a {@link JSONObject}
+ * wrapping the same underlying object.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof JSONObject)) {
+ return false;
+ }
+ return jsObject.equals(((JSONObject) other).jsObject);
}
/**
- * Gets the JSONValue associated with the specified key.
+ * Gets the JSONValue associated with the specified property.
*
- * 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
+ * @param key the property to access
+ * @return the value of the specified property, or <code>null</code> if the
+ * property does not exist
+ * @throws NullPointerException if key is <code>null</code>
*/
public JSONValue get(String key) {
if (key == null) {
- return null;
+ throw new NullPointerException();
}
- JSONValue result = getFront(frontStore, key);
- if (result == null && containsBack(backStore, key)) {
- Object o = removeBack(backStore, key);
- if (o instanceof String) {
- result = new JSONString((String) o);
- } else {
- result = JSONParser.buildValue((JavaScriptObject) o);
- }
- putFront(frontStore, key, result);
- }
- return result;
+ return get0(key);
+ }
+
+ /**
+ * Returns the underlying JavaScript object that this object wraps.
+ */
+ public JavaScriptObject getJavaScriptObject() {
+ return jsObject;
+ }
+
+ @Override
+ public int hashCode() {
+ return jsObject.hashCode();
}
/**
@@ -124,25 +107,22 @@
}
/**
- * Returns keys for which this JSONObject has associations.
- *
- * @return array of keys for which there is a value
+ * Returns the set of properties defined on this JSONObject.
*/
public Set<String> keySet() {
- Set<String> keySet = new HashSet<String>();
- addAllKeysFromJavascriptObject(keySet, frontStore);
- addAllKeysFromJavascriptObject(keySet, backStore);
+ HashSet<String> keySet = new HashSet<String>();
+ addAllKeys(keySet);
return keySet;
}
/**
- * Maps the specified key to the specified value in this JSONObject. If the
- * specified key already has an associated value, it is overwritten.
+ * Assign the specified property to the specified value in this JSONObject. If
+ * the property already has an associated value, it is overwritten.
*
- * @param key the key to associate with the specified value
- * @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
+ * @param key the property to assign
+ * @param jsonValue the value to assign
+ * @return the previous value of the property, or <code>null</code> if the
+ * property did not exist
* @throws NullPointerException if key is <code>null</code>
*/
public JSONValue put(String key, JSONValue jsonValue) {
@@ -150,12 +130,12 @@
throw new NullPointerException();
}
JSONValue previous = get(key);
- putFront(frontStore, key, jsonValue);
+ put0(key, jsonValue);
return previous;
}
/**
- * Determines the number of keys on this object.
+ * Determines the number of properties on this object.
*/
public int size() {
return keySet().size();
@@ -168,28 +148,53 @@
* @return a JSON string representation of this JSONObject instance
*/
@Override
- public native String toString() /*-{
- for (var key in this.@com.google.gwt.json.client.JSONObject::backStore) {
- // Wrap everything in backStore so that frontStore is canonical.
- this.@com.google.gwt.json.client.JSONObject::get(Ljava/lang/String;)(key);
- }
- var out = [];
- out.push("{");
- var first = true;
- for (var key in this.@com.google.gwt.json.client.JSONObject::frontStore) {
- if(first) {
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("{");
+ boolean first = true;
+ List<String> keys = new ArrayList<String>();
+ addAllKeys(keys);
+ for (String key : keys) {
+ if (first) {
first = false;
} else {
- out.push(", ");
+ sb.append(", ");
}
- var subObj =
- (this.@com.google.gwt.json.client.JSONObject::frontStore[key]).
- @com.google.gwt.json.client.JSONValue::toString()();
- out.push(@com.google.gwt.json.client.JSONString::escapeValue(Ljava/lang/String;)(key));
- out.push(":");
- out.push(subObj);
+ sb.append(JSONString.escapeValue(key));
+ sb.append(":");
+ sb.append(get(key));
}
- out.push("}")
- return out.join("");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ native JavaScriptObject getUnwrapper() /*-{
+ return @com.google.gwt.json.client.JSONObject::unwrap(Lcom/google/gwt/json/client/JSONObject;);
+ }-*/;
+
+ private native void addAllKeys(Collection<String> s) /*-{
+ var jsObject = this.@com.google.gwt.json.client.JSONObject::jsObject;
+ for (var key in jsObject) {
+ if (jsObject[key] !== undefined) {
+ s.@java.util.Collection::add(Ljava/lang/Object;)(key);
+ }
+ }
+ }-*/;
+
+ private native JSONValue get0(String key) /*-{
+ var v = this.@com.google.gwt.json.client.JSONObject::jsObject[key];
+ var func = @com.google.gwt.json.client.JSONParser::typeMap[typeof v];
+ return func ? func(v) : @com.google.gwt.json.client.JSONParser::throwUnknownTypeException(Ljava/lang/String;)(typeof v);
+ }-*/;
+
+ private native void put0(String key, JSONValue value) /*-{
+ if (value === null) {
+ value = undefined;
+ } else {
+ var func = value.@com.google.gwt.json.client.JSONValue::getUnwrapper()();
+ value = func(value);
+ }
+ this.@com.google.gwt.json.client.JSONObject::jsObject[key] = value;
}-*/;
}
diff --git a/user/src/com/google/gwt/json/client/JSONParser.java b/user/src/com/google/gwt/json/client/JSONParser.java
index c4e4157..89c9828 100644
--- a/user/src/com/google/gwt/json/client/JSONParser.java
+++ b/user/src/com/google/gwt/json/client/JSONParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 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
@@ -26,19 +26,21 @@
*/
public class JSONParser {
+ static final JavaScriptObject typeMap = initTypeMap();
+
/**
- * Given a jsonString, returns the JSONObject representation. For efficiency,
- * parsing occurs lazily as the structure is requested.
+ * Evaluates a trusted JSON string and returns its JSONValue representation.
+ * CAUTION! For efficiency, this method is implemented using the JavaScript
+ * <code>eval()</code> function, which can execute arbitrary script. DO NOT
+ * pass an untrusted string into this method.
*
- * @param jsonString
- * @return a JSONObject that has been built by parsing the JSON string
+ * @param jsonString a JSON object to parse
+ * @return a JSONValue that has been built by parsing the JSON string
* @throws NullPointerException if <code>jsonString</code> is
* <code>null</code>
* @throws IllegalArgumentException if <code>jsonString</code> is empty
*/
public static JSONValue parse(String jsonString) {
- // Create a JavaScriptObject from the JSON string.
- //
if (jsonString == null) {
throw new NullPointerException();
}
@@ -46,142 +48,96 @@
throw new IllegalArgumentException("empty argument");
}
try {
- Object object = evaluate(jsonString);
- if (object instanceof String) {
- return new JSONString((String) object);
- } else {
- return buildValue((JavaScriptObject) object);
- }
+ return evaluate(jsonString);
} catch (JavaScriptException ex) {
throw new JSONException(ex);
}
}
- /**
- * Returns the {@link JSONValue} for a given {@link JavaScriptObject}.
- *
- * @param jsValue {@link JavaScriptObject} to build a {@link JSONValue} for,
- * this object cannot be a primitive JavaScript type
- * @return a {@link JSONValue} instance for the {@link JavaScriptObject}
- */
- static JSONValue buildValue(JavaScriptObject jsValue) throws JSONException {
-
- if (isNull(jsValue)) {
- return JSONNull.getInstance();
- }
-
- if (isArray(jsValue)) {
- return new JSONArray(jsValue);
- }
-
- if (isBoolean(jsValue)) {
- return JSONBoolean.getInstance(asBoolean(jsValue));
- }
-
- if (isDouble(jsValue)) {
- return new JSONNumber(asDouble(jsValue));
- }
-
- if (isObject(jsValue)) {
- return new JSONObject(jsValue);
- }
-
- /*
- * In practice we should never reach this point. If we do, we cannot make
- * any assumptions about the jsValue.
- */
- throw new JSONException("Unknown JavaScriptObject type");
+ static void throwUnknownTypeException(String typeString) {
+ throw new JSONException("Unexpected typeof result '" + typeString
+ + "'; please report this bug to the GWT team");
}
/**
- * Returns the boolean represented by the jsValue. This method
- * assumes that {@link #isBoolean(JavaScriptObject)} returned
- * <code>true</code>.
- *
- * @param jsValue JavaScript object to convert
- * @return the boolean represented by the jsValue
+ * Called from {@link #initTypeMap()}.
*/
- private static native boolean asBoolean(JavaScriptObject jsValue) /*-{
- return jsValue.valueOf();
- }-*/;
+ @SuppressWarnings("unused")
+ private static JSONValue createBoolean(boolean v) {
+ return JSONBoolean.getInstance(v);
+ }
/**
- * Returns the double represented by jsValue. This method assumes that
- * {@link #isDouble(JavaScriptObject)} returned <code>true</code>.
- *
- * @param jsValue JavaScript object to convert
- * @return the double represented by the jsValue
+ * Called from {@link #initTypeMap()}.
*/
- private static native double asDouble(JavaScriptObject jsValue) /*-{
- return jsValue.valueOf();
- }-*/;
+ @SuppressWarnings("unused")
+ private static JSONValue createNumber(double v) {
+ return new JSONNumber(v);
+ }
/**
- * This method converts the json string into either a String or a
- * JavaScriptObject by simply evaluating the string in JavaScript.
+ * Called from {@link #initTypeMap()}. If we get here, <code>o</code> is
+ * either <code>null</code> (not <code>undefined</code>) or a JavaScript
+ * object.
*/
- private static native Object evaluate(String jsonString) /*-{
- var x = eval('(' + jsonString + ')');
- if (typeof x == 'number' || typeof x == 'array' || typeof x == 'boolean') {
- x = (Object(x));
+ @SuppressWarnings("unused")
+ private static native JSONValue createObject(Object o) /*-{
+ if (!o) {
+ return @com.google.gwt.json.client.JSONNull::getInstance()();
}
- return x;
+ var v = o.valueOf ? o.valueOf() : o;
+ if (v !== o) {
+ // It was a primitive wrapper, unwrap it and try again.
+ var func = @com.google.gwt.json.client.JSONParser::typeMap[typeof v];
+ return func ? func(v) : @com.google.gwt.json.client.JSONParser::throwUnknownTypeException(Ljava/lang/String;)(typeof v);
+ } else if (o instanceof Array || o instanceof $wnd.Array) {
+ // Looks like an Array; wrap as JSONArray.
+ // NOTE: this test can fail for objects coming from a different window,
+ // but we know of no reliable tests to determine if something is an Array
+ // in all cases.
+ return @com.google.gwt.json.client.JSONArray::new(Lcom/google/gwt/core/client/JavaScriptObject;)(o);
+ } else {
+ // This is a basic JavaScript object; wrap as JSONObject.
+ // Subobjects will be created on demand.
+ return @com.google.gwt.json.client.JSONObject::new(Lcom/google/gwt/core/client/JavaScriptObject;)(o);
+ }
}-*/;
/**
- * Returns <code>true</code> if the {@link JavaScriptObject} is a wrapped
- * JavaScript Array.
- *
- * @param jsValue JavaScript object to test
- * @return <code>true</code> if jsValue is a wrapped JavaScript Array
+ * Called from {@link #initTypeMap()}.
*/
- private static native boolean isArray(JavaScriptObject jsValue) /*-{
- return jsValue instanceof Array;
- }-*/;
+ @SuppressWarnings("unused")
+ private static JSONValue createString(String v) {
+ return new JSONString(v);
+ }
/**
- * Returns <code>true</code> if the {@link JavaScriptObject} is a wrapped
- * JavaScript Boolean.
- *
- * @param jsValue JavaScript object to test
- * @return <code>true</code> if jsValue is a wrapped JavaScript Boolean
+ * Called from {@link #initTypeMap()}. This method returns a
+ * <code>null</code> pointer, representing JavaScript <code>undefined</code>.
*/
- private static native boolean isBoolean(JavaScriptObject jsValue) /*-{
- return jsValue instanceof Boolean;
- }-*/;
+ @SuppressWarnings("unused")
+ private static JSONValue createUndefined() {
+ return null;
+ }
/**
- * Returns <code>true</code> if the {@link JavaScriptObject} is a wrapped
- * JavaScript Double.
- *
- * @param jsValue JavaScript object to test
- * @return <code>true</code> if jsValue is a wrapped JavaScript Double
+ * This method converts <code>jsonString</code> into a JSONValue.
*/
- private static native boolean isDouble(JavaScriptObject jsValue) /*-{
- return jsValue instanceof Number;
+ private static native JSONValue evaluate(String jsonString) /*-{
+ var v = eval('(' + jsonString + ')');
+ var func = @com.google.gwt.json.client.JSONParser::typeMap[typeof v];
+ return func ? func(v) : @com.google.gwt.json.client.JSONParser::throwUnknownTypeException(Ljava/lang/String;)(typeof v);
}-*/;
- /**
- * Returns <code>true</code> if the {@link JavaScriptObject} is <code>null</code>
- * or <code>undefined</code>.
- *
- * @param jsValue JavaScript object to test
- * @return <code>true</code> if jsValue is <code>null</code> or
- * <code>undefined</code>
- */
- private static native boolean isNull(JavaScriptObject jsValue) /*-{
- return jsValue == null;
- }-*/;
-
- /**
- * Returns <code>true</code> if the {@link JavaScriptObject} is a JavaScript
- * Object.
- *
- * @param jsValue JavaScript object to test
- * @return <code>true</code> if jsValue is a JavaScript Object
- */
- private static native boolean isObject(JavaScriptObject jsValue) /*-{
- return jsValue instanceof Object;
+ private static native JavaScriptObject initTypeMap() /*-{
+ return {
+ "boolean": @com.google.gwt.json.client.JSONParser::createBoolean(Z),
+ "number": @com.google.gwt.json.client.JSONParser::createNumber(D),
+ "string": @com.google.gwt.json.client.JSONParser::createString(Ljava/lang/String;),
+ "object": @com.google.gwt.json.client.JSONParser::createObject(Ljava/lang/Object;),
+ "function": @com.google.gwt.json.client.JSONParser::createObject(Ljava/lang/Object;),
+ "undefined": @com.google.gwt.json.client.JSONParser::createUndefined(),
+ }
}-*/;
/**
diff --git a/user/src/com/google/gwt/json/client/JSONString.java b/user/src/com/google/gwt/json/client/JSONString.java
index 73bce23..5ad02d0 100644
--- a/user/src/com/google/gwt/json/client/JSONString.java
+++ b/user/src/com/google/gwt/json/client/JSONString.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * Copyright 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
@@ -49,6 +49,14 @@
return out;
}-*/;
+ /**
+ * Called from {@link #getUnwrapper()}.
+ */
+ @SuppressWarnings("unused")
+ private static String unwrap(JSONString value) {
+ return value.value;
+ }
+
private String value;
/**
@@ -64,6 +72,20 @@
this.value = value;
}
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof JSONString)) {
+ return false;
+ }
+ return value.equals(((JSONString) other).value);
+ }
+
+ @Override
+ public int hashCode() {
+ // Just use the underlying String's hashCode.
+ return value.hashCode();
+ }
+
/**
* Returns <code>this</code>, as this is a JSONString.
*/
@@ -87,4 +109,9 @@
public String toString() {
return escapeValue(value);
}
+
+ @Override
+ native JavaScriptObject getUnwrapper() /*-{
+ return @com.google.gwt.json.client.JSONString::unwrap(Lcom/google/gwt/json/client/JSONString;);
+ }-*/;
}
diff --git a/user/src/com/google/gwt/json/client/JSONValue.java b/user/src/com/google/gwt/json/client/JSONValue.java
index 4650109..7c2afae 100644
--- a/user/src/com/google/gwt/json/client/JSONValue.java
+++ b/user/src/com/google/gwt/json/client/JSONValue.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * Copyright 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
@@ -15,6 +15,8 @@
*/
package com.google.gwt.json.client;
+import com.google.gwt.core.client.JavaScriptObject;
+
/**
* The superclass of all JSON value types.
*
@@ -26,6 +28,12 @@
*/
public abstract class JSONValue {
/**
+ * Not subclassable outside this package.
+ */
+ JSONValue() {
+ }
+
+ /**
* Returns a non-null reference if this JSONValue is really a JSONArray.
*
* @return a reference to a JSONArray if this JSONValue is a JSONArray or
@@ -90,5 +98,11 @@
* JSON strings that can be sent from the client to a server.
*/
@Override
- public abstract String toString() throws JSONException;
+ public abstract String toString();
+
+ /**
+ * Internal. Returns a JS func that can unwrap this value. Used from native
+ * code.
+ */
+ abstract JavaScriptObject getUnwrapper();
}
diff --git a/user/test/com/google/gwt/json/client/JSONTest.java b/user/test/com/google/gwt/json/client/JSONTest.java
index 3617e27..de9f9f0 100644
--- a/user/test/com/google/gwt/json/client/JSONTest.java
+++ b/user/test/com/google/gwt/json/client/JSONTest.java
@@ -79,7 +79,7 @@
} else if (expected.isNumber() != null) {
JSONNumber expNum = expected.isNumber();
JSONNumber actNum = actual.isNumber();
- assertEquals(expNum.getValue(), actNum.getValue());
+ assertEquals(expNum.doubleValue(), actNum.doubleValue());
} else if (expected.isObject() != null) {
JSONObject expObj = expected.isObject();
JSONObject actObj = actual.isObject();
@@ -146,7 +146,7 @@
assertEquals("Array size must be 10", 10, array.size());
for (int i = 0; i < 10; i++) {
assertEquals("Array value at " + i + " must be " + i,
- array.get(i).isNumber().getValue(), i, 0.001);
+ array.get(i).isNumber().doubleValue(), i, 0.001);
}
}
@@ -163,6 +163,45 @@
assertFalse(falseVal.isBoolean().booleanValue());
}
+ public void testEquals() {
+ JSONArray array = JSONParser.parse("[]").isArray();
+ assertEquals(array, new JSONArray(array.getJavaScriptObject()));
+
+ assertEquals(JSONBoolean.getInstance(false), JSONBoolean.getInstance(false));
+ assertEquals(JSONBoolean.getInstance(true), JSONBoolean.getInstance(true));
+
+ assertEquals(JSONNull.getInstance(), JSONNull.getInstance());
+
+ assertEquals(new JSONNumber(3.1), new JSONNumber(3.1));
+
+ JSONObject object = JSONParser.parse("{}").isObject();
+ assertEquals(object, new JSONObject(object.getJavaScriptObject()));
+
+ assertEquals(new JSONString("foo"), new JSONString("foo"));
+ }
+
+ public void testHashCode() {
+ JSONArray array = JSONParser.parse("[]").isArray();
+ assertHashCodeEquals(array, new JSONArray(array.getJavaScriptObject()));
+
+ assertHashCodeEquals(JSONBoolean.getInstance(false), JSONBoolean.getInstance(false));
+ assertHashCodeEquals(JSONBoolean.getInstance(true), JSONBoolean.getInstance(true));
+
+ assertHashCodeEquals(JSONNull.getInstance(), JSONNull.getInstance());
+
+ assertHashCodeEquals(new JSONNumber(3.1), new JSONNumber(3.1));
+
+ JSONObject object = JSONParser.parse("{}").isObject();
+ assertHashCodeEquals(object, new JSONObject(object.getJavaScriptObject()));
+
+ assertHashCodeEquals(new JSONString("foo"), new JSONString("foo"));
+ }
+
+ private void assertHashCodeEquals(Object expected, Object actual) {
+ assertEquals("hashCodes are not equal", expected.hashCode(),
+ actual.hashCode());
+ }
+
// Null characters do not work in hosted mode
public void testEscaping() {
JSONObject o = new JSONObject();
@@ -258,17 +297,17 @@
public void testNumberBasics() {
JSONNumber n0 = new JSONNumber(1000);
- assertEquals(1000, n0.getValue(), .000001);
+ assertEquals(1000, n0.doubleValue(), .000001);
assertTrue(n0.isNumber() == n0);
assertNull(n0.isObject());
JSONNumber n1 = new JSONNumber(Integer.MAX_VALUE);
- assertEquals(Integer.MAX_VALUE, n1.getValue(), .00001);
+ assertEquals(Integer.MAX_VALUE, n1.doubleValue(), .00001);
assertTrue(n1.isNumber() == n1);
assertNull(n1.isObject());
JSONNumber n2 = new JSONNumber(Integer.MIN_VALUE);
- assertEquals(Integer.MIN_VALUE, n2.getValue(), .00001);
+ assertEquals(Integer.MIN_VALUE, n2.doubleValue(), .00001);
assertTrue(n2.isNumber() == n2);
assertNull(n2.isObject());
}
@@ -296,7 +335,7 @@
assertEquals("Object size must be 10", 10, objIn.keySet().size());
for (int i = 0; i < 10; i++) {
assertEquals("Object value at 'Object " + i + "' must be " + i,
- objIn.get("Object " + i).isNumber().getValue(), i, 0.001);
+ objIn.get("Object " + i).isNumber().doubleValue(), i, 0.001);
}
}
@@ -327,7 +366,7 @@
assertEquals("\"null\" should be null JSONValue", JSONNull.getInstance(),
JSONParser.parse("null"));
assertEquals("5 should be JSONNumber 5", 5d,
- JSONParser.parse("5").isNumber().getValue(), 0.001);
+ JSONParser.parse("5").isNumber().doubleValue(), 0.001);
assertEquals("\"null\" should be null JSONValue", JSONNull.getInstance(),
JSONParser.parse("null"));
JSONValue somethingHello = JSONParser.parse("[{\"something\":\"hello\"}]");
@@ -402,7 +441,34 @@
object.get(stringAsPrimitive("null")).isString().stringValue());
assertEquals("foo",
object.get(stringAsObject("null")).isString().stringValue());
- assertNull(object.get(null));
+
+ try {
+ assertNull(object.get(null));
+ fail("Expected NullPointerException");
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testUndefined() {
+ JSONObject o = JSONParser.parse("{foo:'foo',bar:null}").isObject();
+ assertEquals(new JSONString("foo"), o.get("foo"));
+ assertEquals(JSONNull.getInstance(), o.get("bar"));
+ assertNull(o.get("baz"));
+
+ o.put("foo", JSONNull.getInstance());
+ assertEquals(JSONNull.getInstance(), o.get("foo"));
+ o.put("foo", null);
+ assertNull(o.get("foo"));
+
+ JSONArray array = JSONParser.parse("['foo',null]").isArray();
+ assertEquals(new JSONString("foo"), array.get(0));
+ assertEquals(JSONNull.getInstance(), array.get(1));
+ assertNull(array.get(2));
+
+ array.set(0, JSONNull.getInstance());
+ assertEquals(JSONNull.getInstance(), array.get(0));
+ array.set(0, null);
+ assertNull(array.get(0));
}
public void testWidget() {