Skip to main content

nula_core/util/
json.rs

1//! JSON serialization helper trait.
2//!
3//! [`JsonUtil`] is implemented automatically for every `serde::Serialize +
4//! serde::DeserializeOwned` type and gives Nostr value objects (events,
5//! filters, messages, …) a uniform `to_json` / `from_json` API. It is the
6//! ergonomic equivalent of `serde_json::to_string` / `serde_json::from_str`
7//! that callers reach for in 99% of cases.
8
9use serde::Serialize;
10use serde::de::DeserializeOwned;
11
12/// Convenience JSON serialization API for Nostr value types.
13///
14/// Auto-implemented for any `T: Serialize + DeserializeOwned` so callers can
15/// just `use nula_core::JsonUtil` and call `event.to_json()`.
16pub trait JsonUtil: Sized + Serialize + DeserializeOwned {
17    /// Serialize to a compact JSON [`String`].
18    ///
19    /// # Errors
20    ///
21    /// Propagates [`serde_json::Error`] when serialization fails. With the
22    /// default `Serialize` implementations of the `nula-core` types this is
23    /// effectively unreachable, but is kept for forward compatibility with
24    /// custom types and `#[serde(skip_serializing_if = ...)]` predicates that
25    /// could fail.
26    fn try_to_json(&self) -> Result<String, serde_json::Error> {
27        serde_json::to_string(self)
28    }
29
30    /// Serialize to a pretty-printed JSON [`String`].
31    ///
32    /// # Errors
33    ///
34    /// See [`Self::try_to_json`].
35    fn try_to_pretty_json(&self) -> Result<String, serde_json::Error> {
36        serde_json::to_string_pretty(self)
37    }
38
39    /// Deserialize from JSON.
40    ///
41    /// # Errors
42    ///
43    /// Returns the underlying [`serde_json::Error`] when the input is not
44    /// valid JSON or does not match the expected schema.
45    fn from_json<S>(json: S) -> Result<Self, serde_json::Error>
46    where
47        S: AsRef<str>,
48    {
49        serde_json::from_str(json.as_ref())
50    }
51}
52
53impl<T> JsonUtil for T where T: Sized + Serialize + DeserializeOwned {}
54
55#[cfg(test)]
56mod tests {
57    use serde::{Deserialize, Serialize};
58
59    use super::*;
60
61    #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
62    struct Sample {
63        a: u32,
64        b: String,
65    }
66
67    #[test]
68    fn round_trip() {
69        let value = Sample {
70            a: 1,
71            b: "hi".to_owned(),
72        };
73        let json = value.try_to_json().unwrap();
74        assert_eq!(json, r#"{"a":1,"b":"hi"}"#);
75        let parsed = Sample::from_json(&json).unwrap();
76        assert_eq!(parsed, value);
77    }
78
79    #[test]
80    fn pretty() {
81        let value = Sample {
82            a: 1,
83            b: "hi".to_owned(),
84        };
85        let json = value.try_to_pretty_json().unwrap();
86        assert!(json.contains('\n'));
87    }
88}