1use std::fmt;
2
3use crate::JsonValue;
4use itertools::Itertools;
5use js::{Array, Ctx, FromAtom, FromJs, IntoJs, Null, Object};
6use serde::Serialize;
7use serde_json::{json, Value};
8
9impl<'js> FromJs<'js> for JsonValue {
10 fn from_js(_ctx: Ctx<'js>, val: js::Value<'js>) -> Result<Self, js::Error> {
11 let v = match val {
12 val if val.type_name() == "null" => Value::Null,
13 val if val.type_name() == "undefined" => Value::Null,
14 val if val.is_bool() => val.as_bool().expect("checked bool").into(),
15 val if val.is_string() => {
16 match val.into_string().expect("checked string").to_string() {
17 Ok(v) => Value::String(v),
18 Err(e) => return Err(e),
19 }
20 }
21 val if val.is_int() => val.as_int().expect("checked int").into(),
22 val if val.is_float() => val.as_float().expect("checked float").into(),
23 val if val.is_array() => {
24 let v = val.as_array().expect("checked array");
25 let mut x = Vec::with_capacity(v.len());
26 for i in v.iter() {
27 let v = i?;
28 let v = JsonValue::from_js(_ctx, v)?;
29 x.push(v.into());
30 }
31 Value::Array(x)
32 }
33 val if val.is_object() => {
34 let v = val.into_object().expect("checked object");
36
37 if v.as_function().is_some() {
39 return Ok(Self::null());
40 }
41 let mut x = json!({});
43 for i in v.props() {
44 let (k, v) = i?;
45 let k = String::from_atom(k)?;
46 let v = JsonValue::from_js(_ctx, v)?;
47 x[k] = v.into();
48 }
49 x
50 }
51 _ => Value::Null,
52 };
53 Ok(v.into())
54 }
55}
56
57impl<'js> IntoJs<'js> for JsonValue {
58 fn into_js(self, ctx: Ctx<'js>) -> Result<js::Value<'js>, js::Error> {
59 match self.0 {
60 Value::Null => Null.into_js(ctx),
61 Value::Bool(v) => Ok(js::Value::new_bool(ctx, v)),
62 Value::Number(num) => {
63 if num.is_f64() {
64 Ok(js::Value::new_float(
65 ctx,
66 num.as_f64().expect("checked f64"),
67 ))
68 } else if num.is_i64() {
69 Ok(js::Value::new_number(
70 ctx,
71 num.as_i64().expect("checked f64") as _,
72 ))
73 } else {
74 Ok(js::Value::new_number(
75 ctx,
76 num.as_u64().expect("checked f64") as _,
77 ))
78 }
79 }
80 Value::String(v) => js::String::from_str(ctx, &v)?.into_js(ctx),
81 Value::Array(v) => {
82 let x = Array::new(ctx)?;
83 for (i, v) in v.into_iter().enumerate() {
84 x.set(i, JsonValue(v).into_js(ctx)?)?;
85 }
86 x.into_js(ctx)
87 }
88 Value::Object(v) => {
89 let x = Object::new(ctx)?;
90 for (k, v) in v.into_iter() {
91 x.set(k, JsonValue(v).into_js(ctx)?)?;
92 }
93 x.into_js(ctx)
94 }
95 }
96 }
97}
98
99impl From<Value> for JsonValue {
100 fn from(v: Value) -> Self {
101 Self(v)
102 }
103}
104
105impl From<JsonValue> for Value {
106 fn from(v: JsonValue) -> Self {
107 v.0
108 }
109}
110
111impl Default for JsonValue {
112 fn default() -> Self {
113 Self(json!({}))
114 }
115}
116
117impl JsonValue {
118 pub fn null() -> Self {
119 Self(Value::Null)
120 }
121
122 pub fn array<T: Serialize>(arr: Vec<T>) -> Self {
123 Self(json!(arr))
124 }
125
126 pub fn object<T: Serialize>(obj: T) -> Self {
127 Self(json!(obj))
128 }
129}
130
131impl fmt::Display for JsonValue {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 match &self.0 {
134 Value::Null => write!(f, "null"),
135 Value::Bool(v) => write!(f, "{}", v),
136 Value::Number(v) => write!(f, "{}", v),
137 Value::String(v) => write!(f, "{}", v),
138 Value::Array(v) => {
139 write!(f, "[")?;
140 write!(f, "{}", v.iter().join(", "))?;
141 write!(f, "]")
142 }
143 Value::Object(_) => {
144 let s = serde_json::to_string_pretty(&self.0).map_err(|_| fmt::Error)?;
145 write!(f, "{}", s)
146 }
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 #[cfg(feature = "console")]
155 use crate::builtins::con::Console;
156 use crate::JsEngine;
157 use anyhow::Result;
158 use js::Function;
159
160 #[js::bind(object, public)]
161 #[quickjs(bare, rename = "print")]
162 #[allow(unused_variables)]
163 fn print(s: String) {
164 println!("{}", s);
165 }
166
167 #[cfg(not(feature = "dispatcher"))]
168 #[tokio::test]
169 async fn json_value_should_be_converted_to_js() -> Result<()> {
170 let engine = JsEngine::create()?;
171 let _ret: Result<()> = engine.context.with(|ctx| {
172 let v = JsonValue::object(json!({
173 "name": "John",
174 "age": 30,
175 "cars": [
176 "Ford",
177 "BMW",
178 "Fiat"
179 ]
180 }));
181 let js = v.clone().into_js(ctx)?;
182 assert_eq!(js.type_name(), "object");
183 let v1 = JsonValue::from_js(ctx, js)?;
184 assert_eq!(v, v1);
185 Ok(())
186 });
187 Ok(())
188 }
189
190 #[cfg(not(feature = "dispatcher"))]
191 #[tokio::test]
192 async fn js_object_might_be_converted_as_null() -> Result<()> {
193 let engine = JsEngine::create()?;
194 let _ret: Result<()> = engine.context.with(|ctx| {
195 let obj = Object::new(ctx)?;
196 obj.set("name", "John")?;
197 #[cfg(feature = "console")]
198 obj.set("obj", Console)?;
199 obj.set("fun", Function::new(ctx, print))?;
200 let js = obj.into_js(ctx)?;
201 let v = JsonValue::from_js(ctx, js)?;
202 #[cfg(feature = "console")]
203 assert_eq!(v.0, json!({ "name": "John", "obj": {}, "fun": null }));
204 #[cfg(not(feature = "console"))]
205 assert_eq!(v.0, json!({ "name": "John", "fun": null }));
206 Ok(())
207 });
208 Ok(())
209 }
210}