Skip to content

Commit 5bf3d88

Browse files
committed
src: implement initial simdjson parser
1 parent 8e41b8d commit 5bf3d88

File tree

8 files changed

+529
-0
lines changed

8 files changed

+529
-0
lines changed

lib/internal/bootstrap/realm.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ const schemelessBlockList = new SafeSet([
129129
'quic',
130130
'test',
131131
'test/reporters',
132+
'json',
132133
]);
133134
// Modules that will only be enabled at run time.
134135
const experimentalModuleList = new SafeSet(['sqlite', 'quic']);

lib/json.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
3+
const {
4+
parse,
5+
} = internalBinding('json_parser');
6+
7+
const {
8+
validateString,
9+
} = require('internal/validators');
10+
11+
/**
12+
* Parse a JSON string using simdjson.
13+
* @param {string} text - The JSON string to parse.
14+
* @returns {unknown} - The parsed value.
15+
*/
16+
function parseJSON(text) {
17+
validateString(text, 'text');
18+
return parse(text);
19+
}
20+
21+
module.exports = {
22+
parse: parseJSON,
23+
};

node.gyp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
'src/node_external_reference.cc',
126126
'src/node_file.cc',
127127
'src/node_http_parser.cc',
128+
'src/node_json_parser.cc',
128129
'src/node_http2.cc',
129130
'src/node_i18n.cc',
130131
'src/node_locks.cc',
@@ -257,6 +258,7 @@
257258
'src/node_file-inl.h',
258259
'src/node_http_common.h',
259260
'src/node_http_common-inl.h',
261+
'src/node_json_parser.h',
260262
'src/node_http2.h',
261263
'src/node_http2_state.h',
262264
'src/node_i18n.h',

src/node_binding.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
V(internal_only_v8) \
6161
V(js_stream) \
6262
V(js_udp_wrap) \
63+
V(json_parser) \
6364
V(locks) \
6465
V(messaging) \
6566
V(modules) \

src/node_external_reference.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class ExternalReferenceRegistry {
8080
V(heap_utils) \
8181
V(http_parser) \
8282
V(internal_only_v8) \
83+
V(json_parser) \
8384
V(locks) \
8485
V(messaging) \
8586
V(mksnapshot) \

src/node_json_parser.cc

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
#include "node_json_parser.h"
2+
#include "env-inl.h"
3+
#include "node_errors.h"
4+
#include "node_external_reference.h"
5+
#include "simdjson.h"
6+
#include "util-inl.h"
7+
8+
#include <string>
9+
10+
namespace node {
11+
namespace json_parser {
12+
13+
using v8::Array;
14+
using v8::Context;
15+
using v8::EscapableHandleScope;
16+
using v8::Exception;
17+
using v8::FunctionCallbackInfo;
18+
using v8::Isolate;
19+
using v8::Local;
20+
using v8::MaybeLocal;
21+
using v8::Number;
22+
using v8::Object;
23+
using v8::String;
24+
using v8::Value;
25+
26+
namespace {
27+
28+
MaybeLocal<Value> ConvertSimdjsonValue(Isolate* isolate,
29+
simdjson::ondemand::value element);
30+
31+
MaybeLocal<Object> ConvertSimdjsonObject(Isolate* isolate,
32+
simdjson::ondemand::object obj) {
33+
EscapableHandleScope handle_scope(isolate);
34+
Local<Context> context = isolate->GetCurrentContext();
35+
Local<Object> result = Object::New(isolate);
36+
37+
for (auto field : obj) {
38+
std::string_view key;
39+
if (field.unescaped_key().get(key)) continue;
40+
41+
simdjson::ondemand::value val;
42+
if (field.value().get(val)) continue;
43+
44+
Local<String> v8_key;
45+
if (!String::NewFromUtf8(isolate,
46+
key.data(),
47+
v8::NewStringType::kNormal,
48+
key.length())
49+
.ToLocal(&v8_key)) {
50+
return MaybeLocal<Object>();
51+
}
52+
53+
Local<Value> v8_value;
54+
if (!ConvertSimdjsonValue(isolate, val).ToLocal(&v8_value)) {
55+
return MaybeLocal<Object>();
56+
}
57+
58+
if (result->Set(context, v8_key, v8_value).IsNothing()) {
59+
return MaybeLocal<Object>();
60+
}
61+
}
62+
63+
return handle_scope.Escape(result);
64+
}
65+
66+
MaybeLocal<Array> ConvertSimdjsonArray(Isolate* isolate,
67+
simdjson::ondemand::array arr) {
68+
EscapableHandleScope handle_scope(isolate);
69+
Local<Context> context = isolate->GetCurrentContext();
70+
71+
size_t count = 0;
72+
for (auto element : arr) {
73+
(void)element;
74+
count++;
75+
}
76+
77+
arr.reset();
78+
79+
Local<Array> result = Array::New(isolate, count);
80+
81+
size_t index = 0;
82+
for (auto element : arr) {
83+
simdjson::ondemand::value val;
84+
if (element.get(val)) continue;
85+
86+
Local<Value> v8_value;
87+
if (!ConvertSimdjsonValue(isolate, val).ToLocal(&v8_value)) {
88+
return MaybeLocal<Array>();
89+
}
90+
91+
if (result->Set(context, index++, v8_value).IsNothing()) {
92+
return MaybeLocal<Array>();
93+
}
94+
}
95+
96+
return handle_scope.Escape(result);
97+
}
98+
99+
MaybeLocal<Value> ConvertSimdjsonValue(Isolate* isolate,
100+
simdjson::ondemand::value element) {
101+
EscapableHandleScope handle_scope(isolate);
102+
103+
simdjson::ondemand::json_type type;
104+
if (element.type().get(type)) {
105+
return handle_scope.Escape(v8::Undefined(isolate));
106+
}
107+
108+
Local<Value> result;
109+
110+
switch (type) {
111+
case simdjson::ondemand::json_type::object: {
112+
simdjson::ondemand::object obj;
113+
if (element.get_object().get(obj)) {
114+
return handle_scope.Escape(v8::Undefined(isolate));
115+
}
116+
Local<Object> obj_result;
117+
if (!ConvertSimdjsonObject(isolate, obj).ToLocal(&obj_result)) {
118+
return MaybeLocal<Value>();
119+
}
120+
result = obj_result;
121+
break;
122+
}
123+
case simdjson::ondemand::json_type::array: {
124+
simdjson::ondemand::array arr;
125+
if (element.get_array().get(arr)) {
126+
return handle_scope.Escape(v8::Undefined(isolate));
127+
}
128+
Local<Array> arr_result;
129+
if (!ConvertSimdjsonArray(isolate, arr).ToLocal(&arr_result)) {
130+
return MaybeLocal<Value>();
131+
}
132+
result = arr_result;
133+
break;
134+
}
135+
case simdjson::ondemand::json_type::string: {
136+
std::string_view str;
137+
if (element.get_string().get(str)) {
138+
return handle_scope.Escape(v8::Undefined(isolate));
139+
}
140+
Local<String> str_result;
141+
if (!String::NewFromUtf8(
142+
isolate, str.data(), v8::NewStringType::kNormal, str.length())
143+
.ToLocal(&str_result)) {
144+
return MaybeLocal<Value>();
145+
}
146+
result = str_result;
147+
break;
148+
}
149+
case simdjson::ondemand::json_type::number: {
150+
simdjson::ondemand::number num;
151+
if (element.get_number().get(num)) {
152+
return handle_scope.Escape(v8::Undefined(isolate));
153+
}
154+
if (num.is_int64()) {
155+
result = Number::New(isolate, static_cast<double>(num.get_int64()));
156+
} else if (num.is_uint64()) {
157+
result = Number::New(isolate, static_cast<double>(num.get_uint64()));
158+
} else {
159+
result = Number::New(isolate, num.get_double());
160+
}
161+
break;
162+
}
163+
case simdjson::ondemand::json_type::boolean: {
164+
bool val;
165+
if (element.get_bool().get(val)) {
166+
return handle_scope.Escape(v8::Undefined(isolate));
167+
}
168+
result = v8::Boolean::New(isolate, val);
169+
break;
170+
}
171+
case simdjson::ondemand::json_type::null: {
172+
result = v8::Null(isolate);
173+
break;
174+
}
175+
case simdjson::ondemand::json_type::unknown:
176+
result = v8::Undefined(isolate);
177+
break;
178+
}
179+
180+
return handle_scope.Escape(result);
181+
}
182+
183+
MaybeLocal<Value> ConvertSimdjsonDocument(Isolate* isolate,
184+
simdjson::ondemand::document& doc,
185+
simdjson::ondemand::json_type type) {
186+
EscapableHandleScope handle_scope(isolate);
187+
Local<Value> result;
188+
189+
switch (type) {
190+
case simdjson::ondemand::json_type::object: {
191+
simdjson::ondemand::object obj;
192+
if (doc.get_object().get(obj)) {
193+
return handle_scope.Escape(v8::Undefined(isolate));
194+
}
195+
Local<Object> obj_result;
196+
if (!ConvertSimdjsonObject(isolate, obj).ToLocal(&obj_result)) {
197+
return MaybeLocal<Value>();
198+
}
199+
result = obj_result;
200+
break;
201+
}
202+
case simdjson::ondemand::json_type::array: {
203+
simdjson::ondemand::array arr;
204+
if (doc.get_array().get(arr)) {
205+
return handle_scope.Escape(v8::Undefined(isolate));
206+
}
207+
Local<Array> arr_result;
208+
if (!ConvertSimdjsonArray(isolate, arr).ToLocal(&arr_result)) {
209+
return MaybeLocal<Value>();
210+
}
211+
result = arr_result;
212+
break;
213+
}
214+
case simdjson::ondemand::json_type::string: {
215+
std::string_view str;
216+
if (doc.get_string().get(str)) {
217+
return handle_scope.Escape(v8::Undefined(isolate));
218+
}
219+
Local<String> str_result;
220+
if (!String::NewFromUtf8(
221+
isolate, str.data(), v8::NewStringType::kNormal, str.length())
222+
.ToLocal(&str_result)) {
223+
return MaybeLocal<Value>();
224+
}
225+
result = str_result;
226+
break;
227+
}
228+
case simdjson::ondemand::json_type::number: {
229+
double val;
230+
if (doc.get_double().get(val)) {
231+
return handle_scope.Escape(v8::Undefined(isolate));
232+
}
233+
result = Number::New(isolate, val);
234+
break;
235+
}
236+
case simdjson::ondemand::json_type::boolean: {
237+
bool val;
238+
if (doc.get_bool().get(val)) {
239+
return handle_scope.Escape(v8::Undefined(isolate));
240+
}
241+
result = v8::Boolean::New(isolate, val);
242+
break;
243+
}
244+
case simdjson::ondemand::json_type::null: {
245+
result = v8::Null(isolate);
246+
break;
247+
}
248+
case simdjson::ondemand::json_type::unknown:
249+
result = v8::Undefined(isolate);
250+
break;
251+
}
252+
253+
return handle_scope.Escape(result);
254+
}
255+
256+
}
257+
258+
void Parse(const FunctionCallbackInfo<Value>& args) {
259+
Isolate* isolate = args.GetIsolate();
260+
261+
if (args.Length() < 1 || !args[0]->IsString()) {
262+
isolate->ThrowException(Exception::TypeError(
263+
FIXED_ONE_BYTE_STRING(isolate, "JSON argument must be a string")));
264+
return;
265+
}
266+
267+
Local<String> v8_str = args[0].As<String>();
268+
String::Utf8Value utf8_str(isolate, v8_str);
269+
std::string json_str(*utf8_str, utf8_str.length());
270+
271+
simdjson::padded_string padded(json_str);
272+
simdjson::ondemand::parser parser;
273+
simdjson::ondemand::document doc;
274+
275+
auto error = parser.iterate(padded).get(doc);
276+
if (error) {
277+
isolate->ThrowException(Exception::SyntaxError(
278+
FIXED_ONE_BYTE_STRING(isolate, "Invalid JSON")));
279+
return;
280+
}
281+
282+
simdjson::ondemand::json_type type;
283+
error = doc.type().get(type);
284+
if (error) {
285+
isolate->ThrowException(Exception::SyntaxError(
286+
FIXED_ONE_BYTE_STRING(isolate, "Invalid JSON")));
287+
return;
288+
}
289+
290+
Local<Value> result;
291+
292+
if (!ConvertSimdjsonDocument(isolate, doc, type).ToLocal(&result)) {
293+
return;
294+
}
295+
296+
args.GetReturnValue().Set(result);
297+
}
298+
299+
void Initialize(Local<Object> target,
300+
Local<Value> unused,
301+
Local<Context> context,
302+
void* priv) {
303+
SetMethodNoSideEffect(context, target, "parse", Parse);
304+
}
305+
306+
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
307+
registry->Register(Parse);
308+
}
309+
310+
}
311+
}
312+
313+
NODE_BINDING_CONTEXT_AWARE_INTERNAL(json_parser,
314+
node::json_parser::Initialize)
315+
NODE_BINDING_EXTERNAL_REFERENCE(json_parser,
316+
node::json_parser::RegisterExternalReferences)

0 commit comments

Comments
 (0)