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}