typst_library/loading/
json.rs

1use ecow::eco_format;
2use typst_syntax::Spanned;
3
4use crate::diag::{At, SourceResult};
5use crate::engine::Engine;
6use crate::foundations::{func, scope, Str, Value};
7use crate::loading::{DataSource, Load, Readable};
8
9/// Reads structured data from a JSON file.
10///
11/// The file must contain a valid JSON value, such as object or array. JSON
12/// objects will be converted into Typst dictionaries, and JSON arrays will be
13/// converted into Typst arrays. Strings and booleans will be converted into the
14/// Typst equivalents, `null` will be converted into `{none}`, and numbers will
15/// be converted to floats or integers depending on whether they are whole
16/// numbers.
17///
18/// Be aware that integers larger than 2<sup>63</sup>-1 will be converted to
19/// floating point numbers, which may result in an approximative value.
20///
21/// The function returns a dictionary, an array or, depending on the JSON file,
22/// another JSON data type.
23///
24/// The JSON files in the example contain objects with the keys `temperature`,
25/// `unit`, and `weather`.
26///
27/// # Example
28/// ```example
29/// #let forecast(day) = block[
30///   #box(square(
31///     width: 2cm,
32///     inset: 8pt,
33///     fill: if day.weather == "sunny" {
34///       yellow
35///     } else {
36///       aqua
37///     },
38///     align(
39///       bottom + right,
40///       strong(day.weather),
41///     ),
42///   ))
43///   #h(6pt)
44///   #set text(22pt, baseline: -8pt)
45///   #day.temperature °#day.unit
46/// ]
47///
48/// #forecast(json("monday.json"))
49/// #forecast(json("tuesday.json"))
50/// ```
51#[func(scope, title = "JSON")]
52pub fn json(
53    engine: &mut Engine,
54    /// A [path]($syntax/#paths) to a JSON file or raw JSON bytes.
55    source: Spanned<DataSource>,
56) -> SourceResult<Value> {
57    let data = source.load(engine.world)?;
58    serde_json::from_slice(data.as_slice())
59        .map_err(|err| eco_format!("failed to parse JSON ({err})"))
60        .at(source.span)
61}
62
63#[scope]
64impl json {
65    /// Reads structured data from a JSON string/bytes.
66    #[func(title = "Decode JSON")]
67    #[deprecated = "`json.decode` is deprecated, directly pass bytes to `json` instead"]
68    pub fn decode(
69        engine: &mut Engine,
70        /// JSON data.
71        data: Spanned<Readable>,
72    ) -> SourceResult<Value> {
73        json(engine, data.map(Readable::into_source))
74    }
75
76    /// Encodes structured data into a JSON string.
77    #[func(title = "Encode JSON")]
78    pub fn encode(
79        /// Value to be encoded.
80        value: Spanned<Value>,
81        /// Whether to pretty print the JSON with newlines and indentation.
82        #[named]
83        #[default(true)]
84        pretty: bool,
85    ) -> SourceResult<Str> {
86        let Spanned { v: value, span } = value;
87        if pretty {
88            serde_json::to_string_pretty(&value)
89        } else {
90            serde_json::to_string(&value)
91        }
92        .map(|v| v.into())
93        .map_err(|err| eco_format!("failed to encode value as JSON ({err})"))
94        .at(span)
95    }
96}