quickjs_runtime/quickjs_utils/
json.rs

1//! serialize and stringify JavaScript objects
2
3use crate::jsutils::JsError;
4use crate::quickjs_utils;
5use crate::quickjsrealmadapter::QuickJsRealmAdapter;
6use crate::quickjsvalueadapter::QuickJsValueAdapter;
7use libquickjs_sys as q;
8use std::ffi::CString;
9
10/// Parse a JSON string into an Object
11/// please note that JSON.parse requires member names to be enclosed in double quotes
12/// so {a: 1} and {'a': 1} will both fail
13/// {"a": 1} will parse ok
14/// # Example
15/// ```dontrun
16/// use quickjs_runtime::esruntimebuilder::EsRuntimeBuilder;
17/// use quickjs_runtime::quickjs_utils::{json, objects, primitives};
18/// use quickjs_runtime::quickjs_utils::json::parse;
19/// let rt = EsRuntimeBuilder::new().build();
20/// rt.add_to_event_queue_sync(|q_js_rt| {
21///     let q_ctx = q_js_rt.get_main_context();
22///     let parse_res = json::parse_q(q_ctx, "{\"aaa\": 165}");
23///     if parse_res.is_err() {
24///         panic!("could not parse: {}", parse_res.err().unwrap());
25///     }
26///     let obj_ref = parse_res.ok().unwrap();
27///     let a_ref = objects::get_property(q_ctx.context, &obj_ref, "aaa").ok().unwrap();
28///     let i = primitives::to_i32(&a_ref).ok().unwrap();
29///     assert_eq!(165, i);
30/// });
31/// rt.gc_sync();
32/// ```
33pub fn parse_q(q_ctx: &QuickJsRealmAdapter, input: &str) -> Result<QuickJsValueAdapter, JsError> {
34    unsafe { parse(q_ctx.context, input) }
35}
36
37/// Parse a JSON string into an Object
38/// # Safety
39/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
40pub unsafe fn parse(
41    context: *mut q::JSContext,
42    input: &str,
43) -> Result<QuickJsValueAdapter, JsError> {
44    let s = CString::new(input).ok().unwrap();
45    let f_n = CString::new("JSON.parse").ok().unwrap();
46
47    let len = input.len();
48
49    let val = q::JS_ParseJSON(context, s.as_ptr(), len as _, f_n.as_ptr());
50
51    let ret = QuickJsValueAdapter::new(context, val, false, true, "json::parse result");
52
53    if ret.is_exception() {
54        if let Some(ex) = QuickJsRealmAdapter::get_exception(context) {
55            Err(ex)
56        } else {
57            Err(JsError::new_str("unknown error while parsing json"))
58        }
59    } else {
60        Ok(ret)
61    }
62}
63/// Stringify an Object in script
64/// # Example
65/// ```rust
66/// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
67/// use quickjs_runtime::quickjs_utils::{json, objects, primitives};
68/// let rt = QuickJsRuntimeBuilder::new().build();
69/// rt.exe_rt_task_in_event_loop(|q_js_rt| {
70///     let q_ctx = q_js_rt.get_main_realm();
71///     let obj_ref = objects::create_object_q(q_ctx).ok().unwrap();
72///     objects::set_property_q(q_ctx, &obj_ref, "a", &primitives::from_i32(741)).ok().unwrap();
73///     let str_ref = json::stringify_q(q_ctx, &obj_ref, None).ok().unwrap();
74///     let str_str = primitives::to_string_q(q_ctx, &str_ref).ok().unwrap();
75///     assert_eq!("{\"a\":741}", str_str);
76/// });
77/// rt.gc_sync();
78/// ```
79pub fn stringify_q(
80    q_ctx: &QuickJsRealmAdapter,
81    input: &QuickJsValueAdapter,
82    opt_space: Option<QuickJsValueAdapter>,
83) -> Result<QuickJsValueAdapter, JsError> {
84    unsafe { stringify(q_ctx.context, input, opt_space) }
85}
86
87/// # Safety
88/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
89pub unsafe fn stringify(
90    context: *mut q::JSContext,
91    input: &QuickJsValueAdapter,
92    opt_space: Option<QuickJsValueAdapter>,
93) -> Result<QuickJsValueAdapter, JsError> {
94    //pub fn JS_JSONStringify(
95    //         ctx: *mut JSContext,
96    //         obj: JSValue,
97    //         replacer: JSValue,
98    //         space0: JSValue,
99    //     ) -> JSValue;
100
101    let space_ref = match opt_space {
102        None => quickjs_utils::new_null_ref(),
103        Some(s) => s,
104    };
105
106    let val = q::JS_JSONStringify(
107        context,
108        *input.borrow_value(),
109        quickjs_utils::new_null(),
110        *space_ref.borrow_value(),
111    );
112    let ret = QuickJsValueAdapter::new(context, val, false, true, "json::stringify result");
113
114    if ret.is_exception() {
115        if let Some(ex) = QuickJsRealmAdapter::get_exception(context) {
116            Err(ex)
117        } else {
118            Err(JsError::new_str("unknown error in json::stringify"))
119        }
120    } else {
121        Ok(ret)
122    }
123}
124
125#[cfg(test)]
126pub mod tests {
127    use crate::facades::tests::init_test_rt;
128    use crate::jsutils::Script;
129    use crate::quickjs_utils::json::parse_q;
130    use crate::quickjs_utils::{get_global_q, json, objects, primitives};
131    use crate::values::JsValueFacade;
132    use std::collections::HashMap;
133
134    #[test]
135    fn test_json() {
136        let rt = init_test_rt();
137
138        log::info!("Starting json test");
139
140        rt.exe_rt_task_in_event_loop(|q_js_rt| {
141            let q_ctx = q_js_rt.get_main_realm();
142
143            let obj = objects::create_object_q(q_ctx).ok().unwrap();
144            objects::set_property_q(q_ctx, &obj, "a", &primitives::from_i32(532))
145                .ok()
146                .unwrap();
147            objects::set_property_q(q_ctx, &obj, "b", &primitives::from_bool(true))
148                .ok()
149                .unwrap();
150            objects::set_property_q(
151                q_ctx,
152                &obj,
153                "c",
154                &primitives::from_string_q(q_ctx, "abcdË").ok().unwrap(),
155            )
156            .ok()
157            .unwrap();
158            let str_res = json::stringify_q(q_ctx, &obj, None).ok().unwrap();
159
160            #[cfg(feature = "bellard")]
161            assert_eq!(str_res.get_ref_count(), 1);
162            let json = str_res.to_string().ok().unwrap();
163            assert_eq!(json, "{\"a\":532,\"b\":true,\"c\":\"abcdË\"}");
164
165            let obj2 = parse_q(q_ctx, json.as_str()).ok().unwrap();
166
167            let prop_c = objects::get_property_q(q_ctx, &obj2, "c").ok().unwrap();
168            assert_eq!("abcdË", prop_c.to_string().ok().unwrap());
169        });
170    }
171
172    #[tokio::test]
173    async fn test_json_arg() {
174        let rt = init_test_rt();
175
176        // init my javascript function
177        rt.eval(
178            None,
179            Script::new(
180                "myFunc.js",
181                r#"
182                function myFunction(argObj) {
183                    console.log("I got an %s", typeof argObj);
184                    console.log("It looks like this %s", argObj);
185                    return "hello " + argObj["key"];
186                }
187            "#,
188            ),
189        )
190        .await
191        .ok()
192        .expect("myFunc failed to parse");
193
194        // parse my obj to json
195        let mut my_json_deserable_object = HashMap::new();
196        my_json_deserable_object.insert("key", "value");
197        let json = serde_json::to_string(&my_json_deserable_object)
198            .ok()
199            .expect("serializing failed");
200
201        let func_res = rt
202            .loop_realm(None, move |_rt, realm| {
203                // this runs in the worker thread for the EventLoop so json String needs to be moved here
204                // now we parse the json to a JsValueRef
205                let js_obj = parse_q(realm, json.as_str())
206                    .ok()
207                    .expect("parsing json failed");
208                // then we can invoke the function with that js_obj as input
209                // get the global obj as function container
210                let global = get_global_q(realm);
211                // invoke the function
212                let func_res = crate::quickjs_utils::functions::invoke_member_function_q(
213                    realm,
214                    &global,
215                    "myFunction",
216                    &[js_obj],
217                );
218                //return the value out of the worker thread as JsValueFacade
219                realm.to_js_value_facade(&func_res.ok().expect("func failed"))
220            })
221            .await;
222
223        let jsv = func_res.ok().expect("got err");
224        assert_eq!(jsv.stringify(), "String: hello value");
225    }
226
227    #[tokio::test]
228    async fn test_json_arg2() {
229        let rt = init_test_rt();
230
231        // init my javascript function
232        rt.eval(
233            None,
234            Script::new(
235                "myFunc.js",
236                r#"
237                function myFunction(argObj) {
238                    console.log("I got an %s", typeof argObj);
239                    console.log("It looks like this %s", argObj);
240                    return "hello " + argObj["key"];
241                }
242            "#,
243            ),
244        )
245        .await
246        .ok()
247        .expect("myFunc failed to parse");
248
249        // parse my obj to json
250        let mut my_json_deserable_object = HashMap::new();
251        my_json_deserable_object.insert("key", "value");
252        let json = serde_json::to_string(&my_json_deserable_object)
253            .ok()
254            .expect("serializing failed");
255
256        let json_js_value_facade = JsValueFacade::JsonStr { json };
257
258        let func_res = rt
259            .invoke_function(None, &[], "myFunction", vec![json_js_value_facade])
260            .await;
261
262        let jsv = func_res.ok().expect("got err");
263        assert_eq!(jsv.stringify(), "String: hello value");
264    }
265}