use serde::{Serialize, de::DeserializeOwned};
use serde_json::Value;
#[derive(Debug, thiserror::Error)]
pub enum JsonExtError {
#[error("json parse error: {0}")]
Parse(#[from] serde_json::Error),
}
pub trait ToJson {
fn to_json(&self) -> Value;
}
impl<T> ToJson for T
where
T: Serialize,
{
fn to_json(&self) -> Value {
serde_json::to_value(self).unwrap_or(Value::Null)
}
}
pub fn from_json<T>(json: &str) -> Result<T, JsonExtError>
where
T: DeserializeOwned,
{
Ok(serde_json::from_str(json)?)
}
#[must_use]
pub fn json_pretty(value: &Value) -> String {
serde_json::to_string_pretty(value).unwrap_or_else(|_| String::from("null"))
}
#[must_use]
pub fn json_merge(a: &Value, b: &Value) -> Value {
match (a, b) {
(Value::Object(a), Value::Object(b)) => {
let mut merged = a.clone();
for (key, value) in b {
let next = merged
.get(key)
.map(|existing| json_merge(existing, value))
.unwrap_or_else(|| value.clone());
merged.insert(key.clone(), next);
}
Value::Object(merged)
}
(_, b) => b.clone(),
}
}
#[cfg(test)]
mod tests {
use super::{ToJson, from_json, json_merge, json_pretty};
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
struct User {
id: u32,
name: String,
}
#[test]
fn to_json_converts_serializable_values() {
let user = User {
id: 1,
name: String::from("Rails"),
};
assert_eq!(user.to_json(), json!({"id": 1, "name": "Rails"}));
}
#[test]
fn from_json_decodes_typed_values() {
let user: User =
from_json("{\"id\":1,\"name\":\"Rails\"}").expect("json should deserialize");
assert_eq!(
user,
User {
id: 1,
name: String::from("Rails")
}
);
}
#[test]
fn from_json_returns_a_typed_error_for_invalid_json() {
let error = from_json::<User>("{").expect_err("invalid json should fail");
assert!(error.to_string().starts_with("json parse error:"));
}
#[test]
fn json_pretty_formats_values_with_indentation() {
let pretty = json_pretty(&json!({"id": 1, "name": "Rails"}));
assert!(pretty.contains('\n'));
assert!(pretty.contains(" \"id\": 1"));
}
#[test]
fn json_merge_prefers_values_from_the_right_hand_side() {
let merged = json_merge(&json!({"name": "Rails"}), &json!({"name": "RustRails"}));
assert_eq!(merged, json!({"name": "RustRails"}));
}
#[test]
fn json_merge_recursively_merges_objects() {
let merged = json_merge(
&json!({"db": {"host": "localhost", "pool": 5}}),
&json!({"db": {"pool": 10, "port": 5432}}),
);
assert_eq!(
merged,
json!({"db": {"host": "localhost", "pool": 10, "port": 5432}})
);
}
#[test]
fn json_merge_replaces_non_object_values() {
let merged = json_merge(&json!("left"), &json!([1, 2, 3]));
assert_eq!(merged, json!([1, 2, 3]));
}
}