Skip to main content

neon/types_impl/extract/
json.rs

1//! Extract JavaScript values with JSON serialization
2//!
3//! For complex objects that implement [`serde::Serialize`] and [`serde::Deserialize`],
4//! it is more ergonomic--and often faster--to extract with JSON serialization. The [`Json`]
5//! extractor automatically calls `JSON.stringify` and `JSON.parse` as necessary.
6//!
7//! ```
8//! use neon::types::extract::Json;
9//!
10//! #[neon::export]
11//! fn sort(Json(mut strings): Json<Vec<String>>) -> Json<Vec<String>> {
12//!     strings.sort();
13//!     Json(strings)
14//! }
15//! ```
16
17use std::{error, fmt};
18
19use crate::{
20    context::{Context, Cx},
21    handle::Handle,
22    object::Object,
23    result::{JsResult, NeonResult},
24    types::{
25        extract::{private, TryFromJs, TryIntoJs},
26        JsError, JsFunction, JsObject, JsValue,
27    },
28};
29
30#[cfg(feature = "napi-6")]
31use crate::{handle::Root, thread::LocalKey};
32
33fn global_json_stringify<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> {
34    cx.global::<JsObject>("JSON")?.get(cx, "stringify")
35}
36
37#[cfg(not(feature = "napi-6"))]
38// N.B.: This is not semantically identical to Node-API >= 6. Patching the global
39// method could cause differences between calls. However, threading a `Root` through
40// would require a significant refactor and "don't do this or things will break" is
41// fairly common in JS.
42fn json_stringify<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> {
43    global_json_stringify(cx)
44}
45
46#[cfg(feature = "napi-6")]
47fn json_stringify<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> {
48    static STRINGIFY: LocalKey<Root<JsFunction>> = LocalKey::new();
49
50    STRINGIFY
51        .get_or_try_init(cx, |cx| global_json_stringify(cx).map(|f| f.root(cx)))
52        .map(|f| f.to_inner(cx))
53}
54
55fn global_json_parse<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> {
56    cx.global::<JsObject>("JSON")?.get(cx, "parse")
57}
58
59#[cfg(not(feature = "napi-6"))]
60fn json_parse<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> {
61    global_json_parse(cx)
62}
63
64#[cfg(feature = "napi-6")]
65fn json_parse<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> {
66    static PARSE: LocalKey<Root<JsFunction>> = LocalKey::new();
67
68    PARSE
69        .get_or_try_init(cx, |cx| global_json_parse(cx).map(|f| f.root(cx)))
70        .map(|f| f.to_inner(cx))
71}
72
73fn parse<'cx>(cx: &mut Cx<'cx>, s: &str) -> JsResult<'cx, JsValue> {
74    let s = cx.string(s).upcast();
75
76    json_parse(cx)?.call(cx, s, [s])
77}
78
79/// Wrapper for converting between `T` and [`JsValue`](crate::types::JsValue) by
80/// serializing with JSON.
81pub struct Json<T>(pub T);
82
83impl<'cx, T> TryFromJs<'cx> for Json<T>
84where
85    for<'de> T: serde::de::Deserialize<'de>,
86{
87    type Error = Error;
88
89    fn try_from_js(
90        cx: &mut Cx<'cx>,
91        v: Handle<'cx, JsValue>,
92    ) -> NeonResult<Result<Self, Self::Error>> {
93        let s = json_stringify(cx)?.call(cx, v, [v])?;
94        let res = match String::try_from_js(cx, s)? {
95            Ok(s) => serde_json::from_str(&s),
96            // If the type was not a `string`, it must be `undefined`
97            Err(_) => T::deserialize(serde::de::value::UnitDeserializer::new()),
98        };
99
100        Ok(res.map(Json).map_err(Error))
101    }
102}
103
104impl<'cx, T> TryIntoJs<'cx> for Json<T>
105where
106    T: serde::Serialize,
107{
108    type Value = JsValue;
109
110    fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
111        TryIntoJs::try_into_js(&self, cx)
112    }
113}
114
115impl<'cx, T> TryIntoJs<'cx> for &Json<T>
116where
117    T: serde::Serialize,
118{
119    type Value = JsValue;
120
121    fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
122        let s = serde_json::to_string(&self.0).or_else(|err| cx.throw_error(err.to_string()))?;
123
124        parse(cx, &s)
125    }
126}
127
128impl<T> private::Sealed for Json<T> {}
129
130impl<T> private::Sealed for &Json<T> {}
131
132/// Error returned when a value is invalid JSON
133pub struct Error(serde_json::Error);
134
135impl fmt::Display for Error {
136    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137        fmt::Display::fmt(&self.0, f)
138    }
139}
140
141impl fmt::Debug for Error {
142    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
143        fmt::Debug::fmt(&self.0, f)
144    }
145}
146
147impl error::Error for Error {}
148
149impl<'cx> TryIntoJs<'cx> for Error {
150    type Value = JsError;
151
152    fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
153        JsError::error(cx, self.to_string())
154    }
155}
156
157impl private::Sealed for Error {}