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, JsString, 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, C>(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 stringify(cx: &mut Cx, v: Handle<JsValue>) -> NeonResult<String> {
56    json_stringify(cx)?
57        .call(cx, v, [v])?
58        .downcast_or_throw::<JsString, _>(cx)
59        .map(|s| s.value(cx))
60}
61
62fn global_json_parse<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> {
63    cx.global::<JsObject>("JSON")?.get(cx, "parse")
64}
65
66#[cfg(not(feature = "napi-6"))]
67fn json_parse<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> {
68    global_json_parse(cx)
69}
70
71#[cfg(feature = "napi-6")]
72fn json_parse<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> {
73    static PARSE: LocalKey<Root<JsFunction>> = LocalKey::new();
74
75    PARSE
76        .get_or_try_init(cx, |cx| global_json_parse(cx).map(|f| f.root(cx)))
77        .map(|f| f.to_inner(cx))
78}
79
80fn parse<'cx>(cx: &mut Cx<'cx>, s: &str) -> JsResult<'cx, JsValue> {
81    let s = cx.string(s).upcast();
82
83    json_parse(cx)?.call(cx, s, [s])
84}
85
86/// Wrapper for converting between `T` and [`JsValue`](crate::types::JsValue) by
87/// serializing with JSON.
88pub struct Json<T>(pub T);
89
90impl<'cx, T> TryFromJs<'cx> for Json<T>
91where
92    for<'de> T: serde::de::Deserialize<'de>,
93{
94    type Error = Error;
95
96    fn try_from_js(
97        cx: &mut Cx<'cx>,
98        v: Handle<'cx, JsValue>,
99    ) -> NeonResult<Result<Self, Self::Error>> {
100        Ok(serde_json::from_str(&stringify(cx, v)?)
101            .map(Json)
102            .map_err(Error))
103    }
104}
105
106impl<'cx, T> TryIntoJs<'cx> for Json<T>
107where
108    T: serde::Serialize,
109{
110    type Value = JsValue;
111
112    fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
113        let s = serde_json::to_string(&self.0).or_else(|err| cx.throw_error(err.to_string()))?;
114
115        parse(cx, &s)
116    }
117}
118
119impl<T> private::Sealed for Json<T> {}
120
121/// Error returned when a value is invalid JSON
122pub struct Error(serde_json::Error);
123
124impl fmt::Display for Error {
125    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126        fmt::Display::fmt(&self.0, f)
127    }
128}
129
130impl fmt::Debug for Error {
131    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
132        fmt::Debug::fmt(&self.0, f)
133    }
134}
135
136impl error::Error for Error {}
137
138impl<'cx> TryIntoJs<'cx> for Error {
139    type Value = JsError;
140
141    fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
142        JsError::error(cx, self.to_string())
143    }
144}
145
146impl private::Sealed for Error {}