mirror of
https://github.com/RGBCube/serenity
synced 2025-05-28 19:25:10 +00:00
LibJS: Add JSON.parse
This commit is contained in:
parent
e8e728454c
commit
b155e64b67
6 changed files with 218 additions and 5 deletions
|
@ -49,6 +49,7 @@
|
||||||
M(IsNotAEvaluatedFrom, "%s is not a %s (evaluated from '%s')") \
|
M(IsNotAEvaluatedFrom, "%s is not a %s (evaluated from '%s')") \
|
||||||
M(JsonBigInt, "Cannot serialize BigInt value to JSON") \
|
M(JsonBigInt, "Cannot serialize BigInt value to JSON") \
|
||||||
M(JsonCircular, "Cannot stringify circular object") \
|
M(JsonCircular, "Cannot stringify circular object") \
|
||||||
|
M(JsonMalformed, "Malformed JSON string") \
|
||||||
M(NotA, "Not a %s object") \
|
M(NotA, "Not a %s object") \
|
||||||
M(NotACtor, "%s is not a constructor") \
|
M(NotACtor, "%s is not a constructor") \
|
||||||
M(NotAFunction, "%s is not a function") \
|
M(NotAFunction, "%s is not a function") \
|
||||||
|
|
|
@ -24,6 +24,9 @@
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/JsonParser.h>
|
||||||
|
#include <AK/JsonObject.h>
|
||||||
|
#include <AK/JsonArray.h>
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
#include <LibJS/Interpreter.h>
|
#include <LibJS/Interpreter.h>
|
||||||
#include <LibJS/Runtime/Array.h>
|
#include <LibJS/Runtime/Array.h>
|
||||||
|
@ -39,7 +42,7 @@ JSONObject::JSONObject()
|
||||||
{
|
{
|
||||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||||
define_native_function("stringify", stringify, 3, attr);
|
define_native_function("stringify", stringify, 3, attr);
|
||||||
define_native_function("parse", parse, 1, attr);
|
define_native_function("parse", parse, 2, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject::~JSONObject()
|
JSONObject::~JSONObject()
|
||||||
|
@ -365,9 +368,119 @@ String JSONObject::quote_json_string(String string)
|
||||||
return builder.to_string();
|
return builder.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
Value JSONObject::parse(Interpreter&)
|
Value JSONObject::parse(Interpreter& interpreter)
|
||||||
{
|
{
|
||||||
return js_undefined();
|
if (!interpreter.argument_count())
|
||||||
|
return js_undefined();
|
||||||
|
auto string = interpreter.argument(0).to_string(interpreter);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
auto reviver = interpreter.argument(1);
|
||||||
|
|
||||||
|
auto json = JsonValue::from_string(string);
|
||||||
|
if (!json.has_value()) {
|
||||||
|
interpreter.throw_exception<SyntaxError>(ErrorType::JsonMalformed);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
Value result = parse_json_value(interpreter, json.value());
|
||||||
|
if (reviver.is_function()) {
|
||||||
|
auto* holder_object = Object::create_empty(interpreter, interpreter.global_object());
|
||||||
|
holder_object->define_property(String::empty(), result);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
return internalize_json_property(interpreter, holder_object, String::empty(), reviver.as_function());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value JSONObject::parse_json_value(Interpreter& interpreter, const JsonValue& value)
|
||||||
|
{
|
||||||
|
if (value.is_object())
|
||||||
|
return Value(parse_json_object(interpreter, value.as_object()));
|
||||||
|
if (value.is_array())
|
||||||
|
return Value(parse_json_array(interpreter, value.as_array()));
|
||||||
|
if (value.is_null())
|
||||||
|
return js_null();
|
||||||
|
#if !defined(KERNEL)
|
||||||
|
if (value.is_double())
|
||||||
|
return Value(value.as_double());
|
||||||
|
#endif
|
||||||
|
if (value.is_number())
|
||||||
|
return Value(value.to_i32(0));
|
||||||
|
if (value.is_string())
|
||||||
|
return js_string(interpreter, value.to_string());
|
||||||
|
if (value.is_bool())
|
||||||
|
return Value(static_cast<bool>(value.as_bool()));
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object* JSONObject::parse_json_object(Interpreter& interpreter, const JsonObject& json_object)
|
||||||
|
{
|
||||||
|
auto* object = Object::create_empty(interpreter, interpreter.global_object());
|
||||||
|
json_object.for_each_member([&object, &interpreter](String key, JsonValue value) {
|
||||||
|
object->put(key, parse_json_value(interpreter, value));
|
||||||
|
});
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array* JSONObject::parse_json_array(Interpreter& interpreter, const JsonArray& json_array)
|
||||||
|
{
|
||||||
|
auto* array = Array::create(interpreter.global_object());
|
||||||
|
size_t index = 0;
|
||||||
|
json_array.for_each([&array, &interpreter, &index](JsonValue value) {
|
||||||
|
array->put(index++, parse_json_value(interpreter, value));
|
||||||
|
});
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value JSONObject::internalize_json_property(Interpreter& interpreter, Object* holder, const PropertyName& name, Function& reviver)
|
||||||
|
{
|
||||||
|
auto value = holder->get(name);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
if (value.is_object()) {
|
||||||
|
auto& value_object = value.as_object();
|
||||||
|
|
||||||
|
auto process_property = [&](const PropertyName& key) {
|
||||||
|
auto element = internalize_json_property(interpreter, &value_object, key, reviver);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return;
|
||||||
|
if (element.is_undefined()) {
|
||||||
|
value_object.delete_property(key);
|
||||||
|
} else {
|
||||||
|
value_object.define_property(key, element, default_attributes, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (value_object.is_array()) {
|
||||||
|
auto length = length_of_array_like(interpreter, value);
|
||||||
|
for (size_t i = 0; i < length; ++i) {
|
||||||
|
process_property(i);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto& entry : value_object.indexed_properties()) {
|
||||||
|
auto value_and_attributes = entry.value_and_attributes(&value_object);
|
||||||
|
if (!value_and_attributes.attributes.is_enumerable())
|
||||||
|
continue;
|
||||||
|
process_property(entry.index());
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
for (auto& [key, metadata] : value_object.shape().property_table_ordered()) {
|
||||||
|
if (!metadata.attributes.is_enumerable())
|
||||||
|
continue;
|
||||||
|
process_property(key);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MarkedValueList arguments(interpreter.heap());
|
||||||
|
arguments.values().append(js_string(interpreter, name.to_string()));
|
||||||
|
arguments.values().append(value);
|
||||||
|
return interpreter.call(reviver, Value(holder), move(arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,6 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <LibJS/Runtime/Object.h>
|
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
class JSONObject final : public Object {
|
class JSONObject final : public Object {
|
||||||
|
@ -52,6 +50,12 @@ private:
|
||||||
static String serialize_json_array(Interpreter&, StringifyState&, Object&);
|
static String serialize_json_array(Interpreter&, StringifyState&, Object&);
|
||||||
static String quote_json_string(String);
|
static String quote_json_string(String);
|
||||||
|
|
||||||
|
// Parse helpers
|
||||||
|
static Object* parse_json_object(Interpreter&, const JsonObject&);
|
||||||
|
static Array* parse_json_array(Interpreter&, const JsonArray&);
|
||||||
|
static Value parse_json_value(Interpreter&, const JsonValue&);
|
||||||
|
static Value internalize_json_property(Interpreter&, Object* holder, const PropertyName& name, Function& reviver);
|
||||||
|
|
||||||
static Value stringify(Interpreter&);
|
static Value stringify(Interpreter&);
|
||||||
static Value parse(Interpreter&);
|
static Value parse(Interpreter&);
|
||||||
};
|
};
|
||||||
|
|
15
Libraries/LibJS/Tests/JSON.parse-reviver.js
Normal file
15
Libraries/LibJS/Tests/JSON.parse-reviver.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
let string = `{"var1":10,"var2":"hello","var3":{"nested":5}}`;
|
||||||
|
|
||||||
|
let object = JSON.parse(string, (key, value) => typeof value === "number" ? value * 2 : value);
|
||||||
|
assertDeepEquals(object, { var1: 20, var2: "hello", var3: { nested: 10 } });
|
||||||
|
|
||||||
|
object = JSON.parse(string, (key, value) => typeof value === "number" ? undefined : value);
|
||||||
|
assertDeepEquals(object, { var2: "hello", var3: {} });
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
43
Libraries/LibJS/Tests/JSON.parse.js
Normal file
43
Libraries/LibJS/Tests/JSON.parse.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(JSON.parse.length === 2);
|
||||||
|
|
||||||
|
const properties = [
|
||||||
|
["5", 5],
|
||||||
|
["null", null],
|
||||||
|
["true", true],
|
||||||
|
["false", false],
|
||||||
|
['"test"', "test"],
|
||||||
|
['[1,2,"foo"]', [1, 2, "foo"]],
|
||||||
|
['{"foo":1,"bar":"baz"}', { foo: 1, bar: "baz" }],
|
||||||
|
];
|
||||||
|
|
||||||
|
properties.forEach(testCase => {
|
||||||
|
assertDeepEquals(JSON.parse(testCase[0]), testCase[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
let syntaxErrors = [
|
||||||
|
undefined,
|
||||||
|
NaN,
|
||||||
|
-NaN,
|
||||||
|
Infinity,
|
||||||
|
-Infinity,
|
||||||
|
'{ "foo" }',
|
||||||
|
'{ foo: "bar" }',
|
||||||
|
"[1,2,3,]",
|
||||||
|
"[1,2,3, ]",
|
||||||
|
'{ "foo": "bar",}',
|
||||||
|
'{ "foo": "bar", }',
|
||||||
|
];
|
||||||
|
|
||||||
|
syntaxErrors.forEach(error => assertThrowsError(() => {
|
||||||
|
JSON.parse(error);
|
||||||
|
}, {
|
||||||
|
error: SyntaxError,
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
|
@ -92,3 +92,40 @@ const assertVisitsAll = (testFunction, expectedOutput) => {
|
||||||
function isClose(a, b) {
|
function isClose(a, b) {
|
||||||
return Math.abs(a - b) < 0.000001;
|
return Math.abs(a - b) < 0.000001;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick and dirty deep equals method.
|
||||||
|
* @param {*} a First value
|
||||||
|
* @param {*} b Second value
|
||||||
|
*/
|
||||||
|
function assertDeepEquals(a, b) {
|
||||||
|
assert(deepEquals(a, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepEquals(a, b) {
|
||||||
|
if (Array.isArray(a))
|
||||||
|
return Array.isArray(b) && deepArrayEquals(a, b);
|
||||||
|
if (typeof a === "object")
|
||||||
|
return typeof b === "object" && deepObjectEquals(a, b);
|
||||||
|
return a === b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepArrayEquals(a, b) {
|
||||||
|
if (a.length !== b.length)
|
||||||
|
return false;
|
||||||
|
for (let i = 0; i < a.length; ++i) {
|
||||||
|
if (!deepEquals(a[i], b[i]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepObjectEquals(a, b) {
|
||||||
|
if (a === null)
|
||||||
|
return b === null;
|
||||||
|
for (let key of Reflect.ownKeys(a)) {
|
||||||
|
if (!deepEquals(a[key], b[key]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue