Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions crates/javy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ rquickjs = { version = "0.10.0", features = [
"bindgen",
"disable-assertions",
] }
rquickjs-serde = { version = "0.3.0", optional = true }
serde = { workspace = true, default-features = true, features = ["derive"] }
serde_json = { workspace = true, optional = true }
serde-transcode = { version = "1.1", optional = true }
Expand All @@ -34,12 +35,12 @@ simd-json = { version = "0.17.0", optional = true, default-features = false, fea
javy-test-macros = { path = "../test-macros/" }

[features]
messagepack = ["rmp-serde", "serde-transcode"]
messagepack = ["rmp-serde", "serde-transcode", "rquickjs-serde"]
# According to our benchmarks and experiments, the fastest and most efficient
# JSON implementation comes from:
# * Using SIMD JSON for parsing
# * Using serde_json for stringifying
# This implementation is behind a feature flag, because of the code size
# implications of enabling by default (due to the extra dependencies) and also
# because the native implementation is probably fine for most use-cases.
json = ["serde_json", "serde-transcode", "simd-json"]
json = ["serde_json", "serde-transcode", "simd-json", "rquickjs-serde"]
22 changes: 20 additions & 2 deletions crates/javy/src/apis/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ use crate::{
Args, hold, json,
quickjs::{
Ctx, Exception, Function, Object, String as JSString, Value,
atom::PredefinedAtom,
function::This,
prelude::{MutFn, Rest},
qjs::JS_GetProperty,
},
to_js_error, val_to_string,
};

use crate::serde::de::get_to_json;

use simd_json::Error as SError;

use anyhow::{Result, anyhow, bail};
Expand Down Expand Up @@ -169,3 +169,21 @@ fn call_json_stringify(args: Args<'_>) -> Result<Value<'_>> {
)),
}
}

fn get_to_json<'a>(value: &Value<'a>) -> Option<Function<'a>> {
let f = unsafe {
JS_GetProperty(
value.ctx().as_raw().as_ptr(),
value.as_raw(),
PredefinedAtom::ToJSON as u32,
)
};
let f = unsafe { Value::from_raw(value.ctx().clone(), f) };
if f.is_function()
&& let Some(f) = f.into_function()
{
Some(f)
} else {
None
}
}
4 changes: 2 additions & 2 deletions crates/javy/src/json.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::quickjs::{Ctx, Value};
use crate::serde::{de::Deserializer, ser::Serializer};
use anyhow::Result;
use rquickjs_serde::{de::Deserializer, ser::Serializer};

/// Transcodes a byte slice containing a JSON encoded payload into a [Value].
pub fn parse<'js>(context: Ctx<'js>, bytes: &mut [u8]) -> Result<Value<'js>> {
Expand All @@ -14,7 +14,7 @@ pub fn parse<'js>(context: Ctx<'js>, bytes: &mut [u8]) -> Result<Value<'js>> {
/// Transcodes a [Value] into a slice of JSON bytes.
pub fn stringify(val: Value<'_>) -> Result<Vec<u8>> {
let mut output: Vec<u8> = Vec::new();
let mut deserializer = Deserializer::from(val);
let mut deserializer = Deserializer::from(val).with_strict();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not to block this PR, but looking at the commentary around with_strict

The implementation tries to make smart guesses when it can, for example the deserializer will fallback to converting BigInt to String. This is likely not what you want if you implement a serialization that is JSON compliant.

This is s why we offer the strict mode, that will stick to what the behaviour that the Javascript specification defines for JSON. Just switch the method to use it.

Would you consider making the default implementation be the compliant one and make the lossy one opt-in (and potentially unsafe)?. It seems like a potentially footgun otherwise.

let mut serializer = serde_json::Serializer::new(&mut output);
serde_transcode::transcode(&mut deserializer, &mut serializer)?;
Ok(output)
Expand Down
3 changes: 1 addition & 2 deletions crates/javy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ use std::str;

mod config;
mod runtime;
mod serde;

use anyhow::{Error, Result, anyhow};
use rquickjs::{
Expand Down Expand Up @@ -129,7 +128,7 @@ pub fn to_js_error(cx: Ctx, e: Error) -> JSError {
Ok(e) => e,
Err(e) => {
// In some cases the original error context is lost i.e. we can't
// retain the orginal JSError when invoking serde_transcode,
// retain the original JSError when invoking serde_transcode,
// particularly for json::stringify. The process of transcoding will
// report the Serializer error, which is totally implementation
// dependent, in this case particular to serde_json::Error. To
Expand Down
4 changes: 2 additions & 2 deletions crates/javy/src/messagepack.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::quickjs::{Ctx, Value};
use crate::serde::{de::Deserializer, ser::Serializer};
use anyhow::Result;
use rquickjs_serde::{de::Deserializer, ser::Serializer};

/// Transcodes a byte slice containing a MessagePack encoded payload into a [`JSValueRef`].
///
Expand All @@ -18,7 +18,7 @@ pub fn transcode_input<'js>(context: Ctx<'js>, bytes: &[u8]) -> Result<Value<'js
/// Transcodes a [`JSValueRef`] into a MessagePack encoded byte vector.
pub fn transcode_output(val: Value<'_>) -> Result<Vec<u8>> {
let mut output = Vec::new();
let mut deserializer = Deserializer::from(val);
let mut deserializer = Deserializer::from(val).with_strict();
let mut serializer = rmp_serde::Serializer::new(&mut output);
serde_transcode::transcode(&mut deserializer, &mut serializer)?;
Ok(output)
Expand Down
Loading