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}