use std::borrow::Borrow; use std::fmt; use itertools::Itertools as _; use serde::{Deserialize, Serialize}; use serde_json::Value; /// Prerendered json. /// /// Both the Display and serde_json::to_string implementations write the serialized json #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(from = "Value")] #[serde(into = "Value")] pub(crate) struct OrderedJson(String); impl OrderedJson { /// If you pass in an array, it will not be sorted. pub(crate) fn serialize(item: T) -> Result { Ok(Self(serde_json::to_string(&item)?)) } /// Serializes and sorts pub(crate) fn array_sorted, I: IntoIterator>(items: I) -> Self { let items = items .into_iter() .sorted_unstable_by(|a, b| a.borrow().cmp(&b.borrow())) .format_with(",", |item, f| f(item.borrow())); Self(format!("[{}]", items)) } pub(crate) fn array_unsorted, I: IntoIterator>(items: I) -> Self { let items = items.into_iter().format_with(",", |item, f| f(item.borrow())); Self(format!("[{items}]")) } } impl fmt::Display for OrderedJson { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl From for OrderedJson { fn from(value: Value) -> Self { let serialized = serde_json::to_string(&value).expect("Serializing a Value to String should never fail"); Self(serialized) } } impl From for Value { fn from(json: OrderedJson) -> Self { serde_json::from_str(&json.0).expect("OrderedJson should always store valid JSON") } } /// For use in JSON.parse('{...}'). /// /// Assumes we are going to be wrapped in single quoted strings. /// /// JSON.parse loads faster than raw JS source, /// so this is used for large objects. #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct EscapedJson(OrderedJson); impl From for EscapedJson { fn from(json: OrderedJson) -> Self { Self(json) } } impl fmt::Display for EscapedJson { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // All these `replace` calls are because we have to go through JS string // for JSON content. // We need to escape double quotes for the JSON let json = self.0.0.replace('\\', r"\\").replace('\'', r"\'").replace("\\\"", "\\\\\""); json.fmt(f) } } #[cfg(test)] mod tests;