blob: e0d83b66c99e92acbd007dd3fe543be06a891db5 [file] [log] [blame]
/*
* 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
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.json.client;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.core.client.JavaScriptObject;
/**
* Parses the string representation of a JSON object into a set of
* JSONValue-derived objects.
*
* @see com.google.gwt.json.client.JSONValue
*/
public class JSONParser {
static final JavaScriptObject typeMap = initTypeMap();
/**
* Evaluates a trusted JSON string and returns its JSONValue representation.
* CAUTION! This method calls the JavaScript <code>eval()</code> function,
* which can execute arbitrary script. DO NOT pass an untrusted string into
* this method.
*
* <p>
* This method has been deprecated. Please call either
* {@link #parseStrict(String)} (for inputs that strictly follow the JSON
* specification) or {@link #parseLenient(String)}. The implementation of this
* method calls parseLenient.
*
* @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
*
* @deprecated use {@link #parseStrict(String)}
*/
@Deprecated
public static JSONValue parse(String jsonString) {
return parseLenient(jsonString);
}
/**
* Evaluates a trusted JSON string and returns its JSONValue representation.
* CAUTION! This method calls the JavaScript {@code eval()} function, which
* can execute arbitrary script. DO NOT pass an untrusted string into this
* method.
*
* @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
* @deprecated use {@link #parseStrict(String)}
*/
@Deprecated
public static JSONValue parseLenient(String jsonString) {
return parse(jsonString, false);
}
/**
* Evaluates a JSON string and returns its JSONValue representation. The
* browser's {@code JSON.parse function} is used.
*
* @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 parseStrict(String jsonString) {
return parse(jsonString, true);
}
static void throwJSONException(String message) {
throw new JSONException(message);
}
static void throwUnknownTypeException(String typeString) {
throw new JSONException("Unexpected typeof result '" + typeString
+ "'; please report this bug to the GWT team");
}
/**
* Called from {@link #initTypeMap()}.
*/
private static JSONValue createBoolean(boolean v) {
return JSONBoolean.getInstance(v);
}
/**
* Called from {@link #initTypeMap()}.
*/
private static JSONValue createNumber(double v) {
return new JSONNumber(v);
}
/**
* 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 JSONValue createObject(Object o) /*-{
if (!o) {
return @com.google.gwt.json.client.JSONNull::getInstance()();
}
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);
}
}-*/;
/**
* Called from {@link #initTypeMap()}.
*/
private static JSONValue createString(String v) {
return new JSONString(v);
}
/**
* Called from {@link #initTypeMap()}. This method returns a <code>null</code>
* pointer, representing JavaScript <code>undefined</code>.
*/
private static JSONValue createUndefined() {
return null;
}
/**
* This method converts <code>jsonString</code> into a JSONValue.
* In strict mode (strict == true), one of two code paths is taken:
* 1) Call JSON.parse, or
* 2) Validate the input and call eval()
*
* In lenient mode (strict == false), eval() is called without validation.
*
* @param strict if true, parse in strict mode.
*/
private static native JSONValue evaluate(String json, boolean strict) /*-{
// Note: we cannot simply call JsonUtils.unsafeEval because it is unable
// to return a result for inputs whose outermost type is 'string' in
// dev mode.
var v;
if (strict) {
try {
v = JSON.parse(json);
} catch (e) {
return @com.google.gwt.json.client.JSONParser::throwJSONException(Ljava/lang/String;)("Error parsing JSON: " + e);
}
} else {
json = @com.google.gwt.core.client.JsonUtils::escapeJsonForEval(Ljava/lang/String;)(json);
try {
v = eval('(' + json + ')');
} catch (e) {
return @com.google.gwt.json.client.JSONParser::throwJSONException(Ljava/lang/String;)("Error parsing JSON: " + e);
}
}
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 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(),
}
}-*/;
private static JSONValue parse(String jsonString, boolean strict) {
if (jsonString == null) {
throw new NullPointerException();
}
if (jsonString.length() == 0) {
throw new IllegalArgumentException("empty argument");
}
try {
return evaluate(jsonString, strict);
} catch (JavaScriptException ex) {
throw new JSONException(ex);
}
}
/**
* Not instantiable.
*/
private JSONParser() {
}
}