mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 01:35:08 +02:00
Replace the custom AK JSON parser with simdjson for parsing JSON in LibJS. This eliminates the intermediate AK::JsonValue object graph, going directly from JSON text to JS::Value. simdjson's on-demand API parses at ~4GB/s and only materializes values as they are accessed, making this both faster and more memory efficient than the previous approach. The AK JSON parser is still used elsewhere (WebDriver protocol, config files, etc.) but LibJS now uses simdjson exclusively for JSON.parse() and JSON.rawJSON().
147 lines
5.2 KiB
JavaScript
147 lines
5.2 KiB
JavaScript
test("basic functionality", () => {
|
|
expect(JSON.parse).toHaveLength(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 => {
|
|
expect(JSON.parse(testCase[0])).toEqual(testCase[1]);
|
|
});
|
|
});
|
|
|
|
test("syntax errors", () => {
|
|
[
|
|
undefined,
|
|
NaN,
|
|
-NaN,
|
|
Infinity,
|
|
-Infinity,
|
|
'{ "foo" }',
|
|
'{ foo: "bar" }',
|
|
"[1,2,3,]",
|
|
"[1,2,3, ]",
|
|
'{ "foo": "bar",}',
|
|
'{ "foo": "bar", }',
|
|
"",
|
|
].forEach(test => {
|
|
expect(() => {
|
|
JSON.parse(test);
|
|
}).toThrow(SyntaxError);
|
|
});
|
|
});
|
|
|
|
test("negative zero", () => {
|
|
["-0", " \n-0", "-0 \t", "\n\t -0\n ", "-0.0"].forEach(testCase => {
|
|
expect(JSON.parse(testCase)).toEqual(-0.0);
|
|
});
|
|
|
|
expect(JSON.parse(-0)).toEqual(0);
|
|
});
|
|
|
|
// The underlying parser resolves decimal numbers by storing the decimal portion in an integer
|
|
// This test handles a regression where the decimal portion was only using a u32 vs. u64
|
|
// and would fail to parse.
|
|
test("long decimal parse", () => {
|
|
expect(JSON.parse("1644452550.6489999294281")).toEqual(1644452550.6489999294281);
|
|
});
|
|
|
|
test("does not truncate large integers", () => {
|
|
expect(JSON.parse("1234567890123")).toEqual(1234567890123);
|
|
expect(JSON.parse("4294967295")).toEqual(4294967295);
|
|
expect(JSON.parse("4294967296")).toEqual(4294967296);
|
|
expect(JSON.parse("4294967297")).toEqual(4294967297);
|
|
expect(JSON.parse("4294967298")).toEqual(4294967298);
|
|
|
|
expect(JSON.parse("2147483647")).toEqual(2147483647);
|
|
expect(JSON.parse("2147483648")).toEqual(2147483648);
|
|
expect(JSON.parse("2147483649")).toEqual(2147483649);
|
|
expect(JSON.parse("2147483650")).toEqual(2147483650);
|
|
|
|
expect(JSON.parse("9007199254740991")).toEqual(9007199254740991);
|
|
expect(JSON.parse("9007199254740992")).toEqual(9007199254740992);
|
|
expect(JSON.parse("9007199254740993")).toEqual(9007199254740993);
|
|
expect(JSON.parse("9007199254740994")).toEqual(9007199254740994);
|
|
expect(JSON.parse("9008199254740994")).toEqual(9008199254740994);
|
|
|
|
expect(JSON.parse("18446744073709551615")).toEqual(18446744073709551615);
|
|
expect(JSON.parse("18446744073709551616")).toEqual(18446744073709551616);
|
|
expect(JSON.parse("18446744073709551617")).toEqual(18446744073709551617);
|
|
});
|
|
|
|
test("number overflow to infinity", () => {
|
|
expect(JSON.parse("1e309")).toBe(Infinity);
|
|
expect(JSON.parse("-1e309")).toBe(-Infinity);
|
|
expect(JSON.parse("1e-400")).toBe(0);
|
|
});
|
|
|
|
test("rejects invalid number formats", () => {
|
|
// Leading zeros not allowed
|
|
expect(() => JSON.parse("01")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse("-01")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse("00")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse("007")).toThrow(SyntaxError);
|
|
|
|
// Trailing decimal point not allowed
|
|
expect(() => JSON.parse("1.")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse("0.")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse("-1.")).toThrow(SyntaxError);
|
|
|
|
// Other invalid formats
|
|
expect(() => JSON.parse("+1")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse(".1")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse("1e")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse("1e+")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse("1e-")).toThrow(SyntaxError);
|
|
});
|
|
|
|
test("rejects trailing content", () => {
|
|
expect(() => JSON.parse("123 garbage")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse("null garbage")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse("true garbage")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse('"string" garbage')).toThrow(SyntaxError);
|
|
expect(() => JSON.parse("[] garbage")).toThrow(SyntaxError);
|
|
expect(() => JSON.parse("{} garbage")).toThrow(SyntaxError);
|
|
});
|
|
|
|
test("string escape sequences", () => {
|
|
expect(JSON.parse('"\\""')).toBe('"');
|
|
expect(JSON.parse('"\\\\"')).toBe("\\");
|
|
expect(JSON.parse('"\\/"')).toBe("/");
|
|
expect(JSON.parse('"\\b"')).toBe("\b");
|
|
expect(JSON.parse('"\\f"')).toBe("\f");
|
|
expect(JSON.parse('"\\n"')).toBe("\n");
|
|
expect(JSON.parse('"\\r"')).toBe("\r");
|
|
expect(JSON.parse('"\\t"')).toBe("\t");
|
|
expect(JSON.parse('"\\u0041"')).toBe("A");
|
|
expect(JSON.parse('"\\u0000"')).toBe("\0");
|
|
});
|
|
|
|
test("unicode and surrogate pairs", () => {
|
|
expect(JSON.parse('"café"')).toBe("café");
|
|
expect(JSON.parse('"日本語"')).toBe("日本語");
|
|
expect(JSON.parse('"\\uD83D\\uDE00"')).toBe("😀");
|
|
expect(JSON.parse('"\\u4e2d\\u6587"')).toBe("中文");
|
|
|
|
// Lone surrogates (valid JSON)
|
|
expect(JSON.parse('"\\uD800"')).toBe("\uD800");
|
|
expect(JSON.parse('"\\uDFFF"')).toBe("\uDFFF");
|
|
});
|
|
|
|
test("whitespace handling", () => {
|
|
expect(JSON.parse(" null")).toBe(null);
|
|
expect(JSON.parse("null ")).toBe(null);
|
|
expect(JSON.parse(" null ")).toBe(null);
|
|
expect(JSON.parse("\t123")).toBe(123);
|
|
expect(JSON.parse("123\n")).toBe(123);
|
|
expect(JSON.parse("\r\n123\r\n")).toBe(123);
|
|
expect(JSON.parse(" { } ")).toEqual({});
|
|
expect(JSON.parse(" [ ] ")).toEqual([]);
|
|
});
|