summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/loading/json.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/loading/json.rs')
-rw-r--r--crates/typst-library/src/loading/json.rs101
1 files changed, 101 insertions, 0 deletions
diff --git a/crates/typst-library/src/loading/json.rs b/crates/typst-library/src/loading/json.rs
new file mode 100644
index 00000000..597cf4cc
--- /dev/null
+++ b/crates/typst-library/src/loading/json.rs
@@ -0,0 +1,101 @@
+use ecow::{eco_format, EcoString};
+use typst_syntax::Spanned;
+
+use crate::diag::{At, SourceResult};
+use crate::engine::Engine;
+use crate::foundations::{func, scope, Str, Value};
+use crate::loading::Readable;
+use crate::World;
+
+/// Reads structured data from a JSON file.
+///
+/// The file must contain a valid JSON value, such as object or array. JSON
+/// objects will be converted into Typst dictionaries, and JSON arrays will be
+/// converted into Typst arrays. Strings and booleans will be converted into the
+/// Typst equivalents, `null` will be converted into `{none}`, and numbers will
+/// be converted to floats or integers depending on whether they are whole
+/// numbers.
+///
+/// Be aware that integers larger than 2<sup>63</sup>-1 will be converted to
+/// floating point numbers, which may result in an approximative value.
+///
+/// The function returns a dictionary, an array or, depending on the JSON file,
+/// another JSON data type.
+///
+/// The JSON files in the example contain objects with the keys `temperature`,
+/// `unit`, and `weather`.
+///
+/// # Example
+/// ```example
+/// #let forecast(day) = block[
+/// #box(square(
+/// width: 2cm,
+/// inset: 8pt,
+/// fill: if day.weather == "sunny" {
+/// yellow
+/// } else {
+/// aqua
+/// },
+/// align(
+/// bottom + right,
+/// strong(day.weather),
+/// ),
+/// ))
+/// #h(6pt)
+/// #set text(22pt, baseline: -8pt)
+/// #day.temperature °#day.unit
+/// ]
+///
+/// #forecast(json("monday.json"))
+/// #forecast(json("tuesday.json"))
+/// ```
+#[func(scope, title = "JSON")]
+pub fn json(
+ /// The engine.
+ engine: &mut Engine,
+ /// Path to a JSON file.
+ ///
+ /// For more details, see the [Paths section]($syntax/#paths).
+ path: Spanned<EcoString>,
+) -> SourceResult<Value> {
+ let Spanned { v: path, span } = path;
+ let id = span.resolve_path(&path).at(span)?;
+ let data = engine.world.file(id).at(span)?;
+ json::decode(Spanned::new(Readable::Bytes(data), span))
+}
+
+#[scope]
+impl json {
+ /// Reads structured data from a JSON string/bytes.
+ #[func(title = "Decode JSON")]
+ pub fn decode(
+ /// JSON data.
+ data: Spanned<Readable>,
+ ) -> SourceResult<Value> {
+ let Spanned { v: data, span } = data;
+ serde_json::from_slice(data.as_slice())
+ .map_err(|err| eco_format!("failed to parse JSON ({err})"))
+ .at(span)
+ }
+
+ /// Encodes structured data into a JSON string.
+ #[func(title = "Encode JSON")]
+ pub fn encode(
+ /// Value to be encoded.
+ value: Spanned<Value>,
+ /// Whether to pretty print the JSON with newlines and indentation.
+ #[named]
+ #[default(true)]
+ pretty: bool,
+ ) -> SourceResult<Str> {
+ let Spanned { v: value, span } = value;
+ if pretty {
+ serde_json::to_string_pretty(&value)
+ } else {
+ serde_json::to_string(&value)
+ }
+ .map(|v| v.into())
+ .map_err(|err| eco_format!("failed to encode value as JSON ({err})"))
+ .at(span)
+ }
+}