1use std::collections::HashMap;
8
9use i_slint_compiler::langtype;
10use i_slint_core::{
11 Brush, Color, SharedString, SharedVector,
12 graphics::Image,
13 model::{Model, ModelRc},
14};
15
16use crate::Value;
17
18pub trait JsonExt
20where
21 Self: Sized,
22{
23 fn to_json(&self) -> Result<serde_json::Value, String>;
25 fn to_json_string(&self) -> Result<String, String>;
27 fn from_json(t: &langtype::Type, value: &serde_json::Value) -> Result<Self, String>;
29 fn from_json_str(t: &langtype::Type, value: &str) -> Result<Self, String>;
31}
32
33impl JsonExt for crate::Value {
34 fn to_json(&self) -> Result<serde_json::Value, String> {
35 value_to_json(self)
36 }
37
38 fn to_json_string(&self) -> Result<String, String> {
39 value_to_json_string(self)
40 }
41
42 fn from_json(t: &langtype::Type, value: &serde_json::Value) -> Result<Self, String> {
43 value_from_json(t, value)
44 }
45
46 fn from_json_str(t: &langtype::Type, value: &str) -> Result<Self, String> {
47 value_from_json_str(t, value)
48 }
49}
50
51pub fn value_from_json(t: &langtype::Type, v: &serde_json::Value) -> Result<Value, String> {
53 use smol_str::ToSmolStr;
54
55 fn string_to_color(s: &str) -> Option<i_slint_core::Color> {
56 i_slint_common::color_parsing::parse_color_literal(s).map(Color::from_argb_encoded)
57 }
58
59 match v {
60 serde_json::Value::Null => Ok(Value::Void),
61 serde_json::Value::Bool(b) => Ok((*b).into()),
62 serde_json::Value::Number(n) => Ok(Value::Number(n.as_f64().unwrap_or(f64::NAN))),
63 serde_json::Value::String(s) => match t {
64 langtype::Type::Enumeration(e) => {
65 let s = if let Some(suffix) = s.strip_prefix(&format!("{}.", e.name)) {
66 suffix.to_smolstr()
67 } else {
68 s.to_smolstr()
69 };
70
71 if e.values.contains(&s) {
72 Ok(Value::EnumerationValue(e.name.to_string(), s.into()))
73 } else {
74 Err(format!("Unexpected value for enum '{}': {}", e.name, s))
75 }
76 }
77 langtype::Type::Color => {
78 if let Some(c) = string_to_color(s) {
79 Ok(Value::Brush(i_slint_core::Brush::SolidColor(c)))
80 } else {
81 Err(format!("Failed to parse color: {s}"))
82 }
83 }
84 langtype::Type::String => Ok(SharedString::from(s.as_str()).into()),
85 langtype::Type::Image => match Image::load_from_path(std::path::Path::new(s)) {
86 Ok(image) => Ok(image.into()),
87 Err(e) => Err(format!("Failed to load image from path: {s}: {e}")),
88 },
89 langtype::Type::Brush => {
90 fn string_to_brush(input: &str) -> Result<i_slint_core::graphics::Brush, String> {
91 fn parse_stops<'a>(
92 it: impl Iterator<Item = &'a str>,
93 ) -> Result<Vec<i_slint_core::graphics::GradientStop>, String>
94 {
95 it.filter(|part| !part.is_empty()).map(|part| {
96 let sub_parts = part.split_whitespace().collect::<Vec<_>>();
97 if sub_parts.len() != 2 {
98 Err("A gradient stop must consist of a color and a position in '%' separated by whitespace".into())
99 } else {
100 let color = string_to_color(sub_parts[0]);
101 let position = {
102 if let Some(percent_value) = sub_parts[1].strip_suffix("%") {
103 percent_value.parse::<f32>().map_err(|_| format!("Could not parse position '{}' as number", sub_parts[1]))
104 } else {
105 Err(format!("The position '{}' does not end in '%'", sub_parts[1]))
106 }
107 };
108
109 match (color, position) {
110 (Some(c), Ok(p)) => Ok(i_slint_core::graphics::GradientStop { color: c, position: p / 100.0}),
111 (_, Err(e)) => Err(e),
112 (None, _) => Err(format!("'{}' is not a color", sub_parts[0])),
113 }
114 }
115 }).collect()
116 }
117
118 let Some(input) = input.strip_suffix(')') else {
119 return Err(format!("No closing ')' in '{input}'"));
120 };
121
122 if let Some(linear) = input.strip_prefix("@linear-gradient(") {
123 let mut split = linear.split(',').map(|p| p.trim());
124
125 let angle = {
126 let Some(angle_part) = split.next() else {
127 return Err(
128 "A linear gradient must start with an angle in 'deg'".into()
129 );
130 };
131
132 angle_part
133 .strip_suffix("deg")
134 .ok_or_else(|| {
135 "A linear brush needs to start with an angle in 'deg'"
136 .to_string()
137 })
138 .and_then(|no| {
139 no.parse::<f32>()
140 .map_err(|_| "Failed to parse angle value".into())
141 })
142 }?;
143
144 Ok(i_slint_core::graphics::LinearGradientBrush::new(
145 angle,
146 parse_stops(split)?.drain(..),
147 )
148 .into())
149 } else if let Some(radial) = input.strip_prefix("@radial-gradient(circle") {
150 let split = radial.split(',').map(|p| p.trim());
151
152 Ok(i_slint_core::graphics::RadialGradientBrush::new_circle(
153 parse_stops(split)?.drain(..),
154 )
155 .into())
156 } else {
157 Err(format!("Could not parse gradient from '{input}'"))
158 }
159 }
160
161 if s.starts_with('#') {
162 if let Some(c) = string_to_color(s) {
163 Ok(Value::Brush(i_slint_core::Brush::SolidColor(c)))
164 } else {
165 Err(format!("Failed to parse color value {s}"))
166 }
167 } else {
168 Ok(Value::Brush(string_to_brush(s)?))
169 }
170 }
171 _ => Err("Value type not supported".into()),
172 },
173 serde_json::Value::Array(array) => match t {
174 langtype::Type::Array(it) => {
175 Ok(Value::Model(ModelRc::new(i_slint_core::model::SharedVectorModel::from(
176 array
177 .iter()
178 .map(|v| value_from_json(it, v))
179 .collect::<Result<SharedVector<Value>, String>>()?,
180 ))))
181 }
182 _ => Err("Got an array where none was expected".into()),
183 },
184 serde_json::Value::Object(obj) => match t {
185 langtype::Type::Struct(s) => {
186 let mut value = crate::Struct(
187 obj.iter()
188 .map(|(k, v)| {
189 let k = crate::api::normalize_identifier(k);
190 match s.fields.get(&k) {
191 Some(t) => value_from_json(t, v).map(|v| (k, v)),
192 None => Err(format!("Found unknown field in struct: {k}")),
193 }
194 })
195 .collect::<Result<HashMap<smol_str::SmolStr, Value>, _>>()?,
196 );
197 crate::eval::fill_missing_struct_fields(&mut value, s);
198 Ok(value.into())
199 }
200 _ => Err("Got a struct where none was expected".into()),
201 },
202 }
203}
204
205pub fn value_from_json_str(t: &langtype::Type, v: &str) -> Result<Value, String> {
207 let value = serde_json::from_str(v).map_err(|e| format!("Failed to parse JSON: {e}"))?;
208 Value::from_json(t, &value)
209}
210
211pub fn value_to_json(value: &Value) -> Result<serde_json::Value, String> {
213 fn color_to_string(color: &Color) -> String {
214 let a = color.alpha();
215 let r = color.red();
216 let g = color.green();
217 let b = color.blue();
218
219 if a == 255 {
220 format!("#{r:02x}{g:02x}{b:02x}")
221 } else {
222 format!("#{r:02x}{g:02x}{b:02x}{a:02x}")
223 }
224 }
225
226 fn gradient_to_string_helper<'a>(
227 prefix: String,
228 stops: impl Iterator<Item = &'a i_slint_core::graphics::GradientStop>,
229 ) -> serde_json::Value {
230 let mut gradient = prefix;
231
232 for stop in stops {
233 gradient += &format!(", {} {}%", color_to_string(&stop.color), stop.position * 100.0);
234 }
235
236 gradient += ")";
237
238 serde_json::Value::String(gradient)
239 }
240
241 match value {
242 Value::Void => Ok(serde_json::Value::Null),
243 Value::Bool(b) => Ok((*b).into()),
244 Value::Number(n) => {
245 let r = if *n == n.round() {
246 if *n >= 0.0 {
247 serde_json::Number::from_u128(*n as u128)
248 } else {
249 serde_json::Number::from_i128(*n as i128)
250 }
251 } else {
252 serde_json::Number::from_f64(*n)
253 };
254 if let Some(r) = r {
255 Ok(serde_json::Value::Number(r))
256 } else {
257 Err(format!("Could not convert {n} into a number"))
258 }
259 }
260 Value::EnumerationValue(e, v) => Ok(serde_json::Value::String(format!("{e}.{v}"))),
261 Value::String(shared_string) => Ok(serde_json::Value::String(shared_string.to_string())),
262 Value::Image(image) => {
263 if let Some(p) = image.path() {
264 Ok(serde_json::Value::String(format!("{}", p.to_string_lossy())))
265 } else {
266 Err("Cannot serialize an image without a path".into())
267 }
268 }
269 Value::Model(model_rc) => Ok(serde_json::Value::Array(
270 model_rc.iter().map(|v| v.to_json()).collect::<Result<Vec<_>, _>>()?,
271 )),
272 Value::Struct(s) => Ok(serde_json::Value::Object(
273 s.iter()
274 .map(|(k, v)| v.to_json().map(|v| (k.to_string(), v)))
275 .collect::<Result<serde_json::Map<_, _>, _>>()?,
276 )),
277 Value::Brush(brush) => match brush {
278 Brush::SolidColor(color) => Ok(serde_json::Value::String(color_to_string(color))),
279 Brush::LinearGradient(lg) => Ok(gradient_to_string_helper(
280 format!("@linear-gradient({}deg", lg.angle()),
281 lg.stops(),
282 )),
283 Brush::RadialGradient(rg) => {
284 Ok(gradient_to_string_helper("@radial-gradient(circle".into(), rg.stops()))
285 }
286 _ => Err("Cannot serialize an unknown brush type".into()),
287 },
288 Value::PathData(_) => Err("Cannot serialize path data".into()),
289 Value::EasingCurve(_) => Err("Cannot serialize a easing curve".into()),
290 _ => Err("Cannot serialize an unknown value type".into()),
291 }
292}
293
294pub fn value_to_json_string(value: &Value) -> Result<String, String> {
296 Ok(value_to_json(value)?.to_string())
297}
298
299#[test]
300fn test_from_json() {
301 let v = value_from_json_str(&langtype::Type::Void, "null").unwrap();
302 assert_eq!(v, Value::Void);
303 let v = Value::from_json_str(&langtype::Type::Void, "null").unwrap();
304 assert_eq!(v, Value::Void);
305
306 let v = value_from_json_str(&langtype::Type::Float32, "42.0").unwrap();
307 assert_eq!(v, Value::Number(42.0));
308
309 let v = value_from_json_str(&langtype::Type::Int32, "23").unwrap();
310 assert_eq!(v, Value::Number(23.0));
311
312 let v = value_from_json_str(&langtype::Type::String, "\"a string with \\\\ escape\"").unwrap();
313 assert_eq!(v, Value::String("a string with \\ escape".into()));
314
315 let v = value_from_json_str(&langtype::Type::Color, "\"#0ab0cdff\"").unwrap();
316 assert_eq!(v, Value::Brush(Brush::SolidColor(Color::from_argb_u8(0xff, 0x0a, 0xb0, 0xcd))));
317 let v = value_from_json_str(&langtype::Type::Brush, "\"#0ab0cdff\"").unwrap();
318 assert_eq!(v, Value::Brush(Brush::SolidColor(Color::from_argb_u8(0xff, 0x0a, 0xb0, 0xcd))));
319 assert_eq!(v, Value::Brush(Brush::SolidColor(Color::from_argb_u8(0xff, 0x0a, 0xb0, 0xcd))));
320 let v = value_from_json_str(
321 &langtype::Type::Brush,
322 "\"@linear-gradient(42deg, #ff0000ff 0%, #00ff00ff 50%, #0000ffff 100%)\"",
323 )
324 .unwrap();
325 assert_eq!(
326 v,
327 Value::Brush(Brush::LinearGradient(i_slint_core::graphics::LinearGradientBrush::new(
328 42.0,
329 vec![
330 i_slint_core::graphics::GradientStop {
331 position: 0.0,
332 color: Color::from_argb_u8(0xff, 0xff, 0x00, 0x00)
333 },
334 i_slint_core::graphics::GradientStop {
335 position: 0.5,
336 color: Color::from_argb_u8(0xff, 0x00, 0xff, 0x00)
337 },
338 i_slint_core::graphics::GradientStop {
339 position: 1.0,
340 color: Color::from_argb_u8(0xff, 0x00, 0x00, 0xff)
341 }
342 ]
343 .drain(..)
344 )))
345 );
346 assert!(
347 value_from_json_str(
348 &langtype::Type::Brush,
349 "\"@linear-gradient(foobar, #ff0000ff 0%, #00ff00ff 50%, #0000ffff 100%)\""
350 )
351 .is_err()
352 );
353 assert!(
354 value_from_json_str(
355 &langtype::Type::Brush,
356 "\"@linear-gradient(#ff0000ff 0%, #00ff00ff 50%, #0000ffff 100%)\""
357 )
358 .is_err()
359 );
360 assert!(
361 value_from_json_str(
362 &langtype::Type::Brush,
363 "\"@linear-gradient(90turns, #ff0000ff 0%, #00ff00ff 50%, #0000ffff 100%)\""
364 )
365 .is_err()
366 );
367 assert!(
368 value_from_json_str(
369 &langtype::Type::Brush,
370 "\"@linear-gradient(xfdeg, #ff0000ff 0%, #00ff00ff 50%, #0000ffff 100%)\""
371 )
372 .is_err()
373 );
374 assert!(
375 value_from_json_str(
376 &langtype::Type::Brush,
377 "\"@linear-gradient(90deg, #xf0000ff 0%, #00ff00ff 50%, #0000ffff 100%)\""
378 )
379 .is_err()
380 );
381 assert!(
382 value_from_json_str(
383 &langtype::Type::Brush,
384 "\"@linear-gradient(90deg, #ff0000ff 0, #00ff00ff 50%, #0000ffff 100%)\""
385 )
386 .is_err()
387 );
388
389 let v = value_from_json_str(
390 &langtype::Type::Brush,
391 "\"@radial-gradient(circle, #ff0000ff 0%, #00ff00ff 50%, #0000ffff 100%)\"",
392 )
393 .unwrap();
394 assert_eq!(
395 v,
396 Value::Brush(Brush::RadialGradient(
397 i_slint_core::graphics::RadialGradientBrush::new_circle(
398 vec![
399 i_slint_core::graphics::GradientStop {
400 position: 0.0,
401 color: Color::from_argb_u8(0xff, 0xff, 0x00, 0x00)
402 },
403 i_slint_core::graphics::GradientStop {
404 position: 0.5,
405 color: Color::from_argb_u8(0xff, 0x00, 0xff, 0x00)
406 },
407 i_slint_core::graphics::GradientStop {
408 position: 1.0,
409 color: Color::from_argb_u8(0xff, 0x00, 0x00, 0xff)
410 }
411 ]
412 .drain(..)
413 )
414 ))
415 );
416 assert!(
417 value_from_json_str(
418 &langtype::Type::Brush,
419 "\"@radial-gradient(foobar, #ff0000ff 0%, #00ff00ff 50%, #0000ffff 100%)\""
420 )
421 .is_err()
422 );
423 assert!(
424 value_from_json_str(
425 &langtype::Type::Brush,
426 "\"@radial-gradient(circle, #xf0000ff 0%, #00ff00ff 50%, #0000ffff 100%)\""
427 )
428 .is_err()
429 );
430 assert!(
431 value_from_json_str(
432 &langtype::Type::Brush,
433 "\"@radial-gradient(circle, #ff0000ff 1000px, #00ff00ff 50%, #0000ffff 100%)\""
434 )
435 .is_err()
436 );
437 assert!(
438 value_from_json_str(
439 &langtype::Type::Brush,
440 "\"@radial-gradient(circle, #ff0000ff 0% #00ff00ff 50%, #0000ffff 100%)\""
441 )
442 .is_err()
443 );
444 assert!(
445 value_from_json_str(
446 &langtype::Type::Brush,
447 "\"@radial-gradient(circle, #ff0000ff, #0000ffff)\""
448 )
449 .is_err()
450 );
451
452 assert!(
453 value_from_json_str(
454 &langtype::Type::Brush,
455 "\"@radial-gradient(conical, #ff0000ff 0%, #00ff00ff 50%, #0000ffff 100%)\""
456 )
457 .is_err()
458 );
459
460 assert!(
461 value_from_json_str(
462 &langtype::Type::Brush,
463 "\"@other-gradient(circle, #ff0000ff 0%, #00ff00ff 50%, #0000ffff 100%)\""
464 )
465 .is_err()
466 );
467}
468
469#[test]
470fn test_from_json_struct_missing_field() {
471 let ty = langtype::Type::Struct(std::sync::Arc::new(langtype::Struct::new(
472 [("name".into(), langtype::Type::String), ("on".into(), langtype::Type::Bool)]
473 .into_iter()
474 .collect(),
475 langtype::StructName::None,
476 )));
477 let v = value_from_json_str(&ty, r#"{"on": true}"#).unwrap();
478 let Value::Struct(s) = v else { panic!("not a struct: {v:?}") };
479 assert_eq!(s.get_field("on"), Some(&Value::Bool(true)));
480 assert_eq!(s.get_field("name"), Some(&Value::String(Default::default())));
481}
482
483#[test]
484fn test_to_json() {
485 let v = value_to_json_string(&Value::Void).unwrap();
486 assert_eq!(&v, "null");
487 let v = Value::Void.to_json_string().unwrap();
488 assert_eq!(&v, "null");
489
490 let v = value_to_json_string(&Value::Number(23.0)).unwrap();
491 assert_eq!(&v, "23");
492
493 let v = value_to_json_string(&Value::Number(4.2)).unwrap();
494 assert_eq!(&v, "4.2");
495
496 let v = value_to_json_string(&Value::EnumerationValue("Foo".to_string(), "bar".to_string()))
497 .unwrap();
498 assert_eq!(&v, "\"Foo.bar\"");
499
500 let v = value_to_json_string(&Value::String("Hello World with \\ escaped".into())).unwrap();
501 assert_eq!(&v, "\"Hello World with \\\\ escaped\"");
502
503 let buffer = i_slint_core::graphics::SharedPixelBuffer::new(2, 2);
505 assert!(value_to_json_string(&Value::Image(Image::from_rgb8(buffer))).is_err());
506
507 let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
509 .join("../../logo/MadeWithSlint-logo-dark.png")
510 .canonicalize()
511 .unwrap();
512 let v = value_to_json_string(&Value::Image(Image::load_from_path(&path).unwrap())).unwrap();
513 let path = path.to_string_lossy().replace("\\", "\\\\");
515 assert_eq!(v, format!("\"{path}\""));
516
517 let v = value_to_json_string(&Value::Bool(true)).unwrap();
518 assert_eq!(&v, "true");
519
520 let v = value_to_json_string(&Value::Bool(false)).unwrap();
521 assert_eq!(&v, "false");
522
523 let model: ModelRc<Value> = std::rc::Rc::new(i_slint_core::model::VecModel::from(vec![
524 Value::Bool(true),
525 Value::Bool(false),
526 ]))
527 .into();
528 let v = value_to_json_string(&Value::Model(model)).unwrap();
529 assert_eq!(&v, "[true,false]");
530
531 let v = value_to_json_string(&Value::Struct(crate::Struct::from_iter([
532 ("kind".to_string(), Value::EnumerationValue("test".to_string(), "foo".to_string())),
533 ("is_bool".to_string(), Value::Bool(false)),
534 ("string-value".to_string(), Value::String("some string".into())),
535 ])))
536 .unwrap();
537 assert_eq!(&v, "{\"is-bool\":false,\"kind\":\"test.foo\",\"string-value\":\"some string\"}");
538
539 let v = value_to_json_string(&Value::Brush(Brush::SolidColor(Color::from_argb_u8(
540 0xff, 0x0a, 0xb0, 0xcd,
541 ))))
542 .unwrap();
543 assert_eq!(v, "\"#0ab0cd\"".to_string());
544
545 let v = value_to_json_string(&Value::Brush(Brush::LinearGradient(
546 i_slint_core::graphics::LinearGradientBrush::new(
547 42.0,
548 vec![
549 i_slint_core::graphics::GradientStop {
550 position: 0.0,
551 color: Color::from_argb_u8(0xff, 0xff, 0x00, 0x00),
552 },
553 i_slint_core::graphics::GradientStop {
554 position: 0.5,
555 color: Color::from_argb_u8(0xff, 0x00, 0xff, 0x00),
556 },
557 i_slint_core::graphics::GradientStop {
558 position: 1.0,
559 color: Color::from_argb_u8(0xff, 0x00, 0x00, 0xff),
560 },
561 ]
562 .drain(..),
563 ),
564 )))
565 .unwrap();
566 assert_eq!(&v, "\"@linear-gradient(42deg, #ff0000 0%, #00ff00 50%, #0000ff 100%)\"");
567
568 let v = value_to_json_string(&Value::Brush(Brush::RadialGradient(
569 i_slint_core::graphics::RadialGradientBrush::new_circle(
570 vec![
571 i_slint_core::graphics::GradientStop {
572 position: 0.0,
573 color: Color::from_argb_u8(0xff, 0xff, 0x00, 0x00),
574 },
575 i_slint_core::graphics::GradientStop {
576 position: 0.5,
577 color: Color::from_argb_u8(0xff, 0x00, 0xff, 0x00),
578 },
579 i_slint_core::graphics::GradientStop {
580 position: 1.0,
581 color: Color::from_argb_u8(0xff, 0x00, 0x00, 0xff),
582 },
583 ]
584 .drain(..),
585 ),
586 )))
587 .unwrap();
588 assert_eq!(&v, "\"@radial-gradient(circle, #ff0000 0%, #00ff00 50%, #0000ff 100%)\"");
589}