1pub struct JSON<'a>(pub &'a dyn ToJSON);
2
3#[macro_export]
4macro_rules! json {
5 {$( $key:ident : $val:expr ),* $(,)?} => {{
6 let mut buf = String::new();
7 let mut builder = Builder::new(&mut buf);
8 let pairs: &[(&str, JSON)] = &[$(
9 (stringify!($key), JSON(&$val)),
10 )*];
11 for (key, value) in pairs {
12 builder.add(key, value);
13 }
14 builder.end();
15 buf
16 }};
17}
18
19pub struct Builder<'a> {
20 started: bool,
21 buf: &'a mut String,
22}
23
24impl<'a> Builder<'a> {
25 pub fn new(buf: &'a mut String) -> Self {
26 buf.push('{');
27 Self {
28 started: false,
29 buf,
30 }
31 }
32
33 pub fn add(&mut self, key: &str, value: impl ToJSON) {
34 if self.started {
35 self.buf.push(',');
36 }
37 self.buf.push('"');
38 self.buf.push_str(key);
40 self.buf.push_str("\":");
41 ToJSON::append_as_json_string(&value, self.buf);
42 self.started = true;
43 }
44
45 pub fn end(self) {
46 self.buf.push('}');
47 }
48}
49
50pub trait ToJSON {
52 fn as_json_string(&self) -> String {
53 let mut buf = String::new();
54 ToJSON::append_as_json_string(self, &mut buf);
55 buf
56 }
57
58 fn append_as_json_string(&self, buf: &mut String);
59}
60
61impl ToJSON for &str {
62 fn append_as_json_string(&self, buf: &mut String) {
63 buf.push('"');
64 buf.push_str(&escape_json_string(self));
65 buf.push('"')
66 }
67}
68
69impl ToJSON for String {
70 fn append_as_json_string(&self, buf: &mut String) {
71 ToJSON::append_as_json_string(&self.as_str(), buf)
72 }
73}
74
75impl<T: ToJSON> ToJSON for &[T] {
76 fn append_as_json_string(&self, buf: &mut String) {
77 buf.push('[');
78 for (idx, item) in self.iter().enumerate() {
79 if idx > 0 {
80 buf.push(',')
81 }
82 ToJSON::append_as_json_string(item, buf)
83 }
84 buf.push(']');
85 }
86}
87
88impl<T: ToJSON> ToJSON for Vec<T> {
89 fn append_as_json_string(&self, buf: &mut String) {
90 ToJSON::append_as_json_string(&self.as_slice(), buf)
91 }
92}
93
94impl<T1: ToJSON, T2: ToJSON> ToJSON for (T1, T2) {
95 fn append_as_json_string(&self, buf: &mut String) {
96 buf.push('[');
97 ToJSON::append_as_json_string(&self.0, buf);
98 buf.push(',');
99 ToJSON::append_as_json_string(&self.1, buf);
100 buf.push(']');
101 }
102}
103
104impl<T1: ToJSON, T2: ToJSON, T3: ToJSON> ToJSON for (T1, T2, T3) {
105 fn append_as_json_string(&self, buf: &mut String) {
106 buf.push('[');
107 ToJSON::append_as_json_string(&self.0, buf);
108 buf.push(',');
109 ToJSON::append_as_json_string(&self.1, buf);
110 buf.push(',');
111 ToJSON::append_as_json_string(&self.2, buf);
112 buf.push(']');
113 }
114}
115
116impl<K: AsRef<str>, V: ToJSON> ToJSON for std::collections::HashMap<K, V> {
117 fn append_as_json_string(&self, buf: &mut String) {
118 buf.push('{');
119 for (idx, (key, value)) in self.iter().enumerate() {
120 if idx > 0 {
121 buf.push(',')
122 }
123 buf.push('"');
124 buf.push_str(&escape_json_string(key.as_ref()));
125 buf.push_str("\":");
126 ToJSON::append_as_json_string(value, buf);
127 }
128 buf.push('}');
129 }
130}
131
132impl ToJSON for bool {
133 fn append_as_json_string(&self, buf: &mut String) {
134 buf.push_str(match self {
135 true => "true",
136 false => "false",
137 })
138 }
139}
140
141macro_rules! create_json_from_to_string_implementation {
142 ($($T:ty),*) => {
143 $(
144 impl ToJSON for $T {
145 fn append_as_json_string(&self, buf: &mut String) {
146 buf.push_str(&self.to_string())
147 }
148 }
149 )*
150 }
151}
152
153create_json_from_to_string_implementation![u8, u16, u32, u64, i8, i16, i32, i64, f32, f64];
155
156impl ToJSON for JSON<'_> {
157 fn append_as_json_string(&self, buf: &mut String) {
158 self.0.append_as_json_string(buf)
159 }
160}
161
162impl ToJSON for &'_ JSON<'_> {
163 fn append_as_json_string(&self, buf: &mut String) {
164 self.0.append_as_json_string(buf)
165 }
166}
167
168pub fn escape_json_string(on: &str) -> std::borrow::Cow<'_, str> {
169 let mut result = std::borrow::Cow::Borrowed("");
170 let mut start = 0;
171 for (index, matched) in on.match_indices(['\"', '\n', '\t', '\\']) {
172 result += &on[start..index];
173 result += "\\";
174 result += match matched {
176 "\"" => "\"",
177 "\\" => "\\",
178 "\n" => "n",
179 "\t" => "t",
180 _ => unreachable!(),
181 };
182 start = index + 1;
183 }
184 result += &on[start..];
185 result
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn basic_object() {
194 let object = json! {
195 x: 78u32,
196 y: 72.4f64,
197 z: "thing"
198 };
199 assert_eq!(object, r#"{"x":78,"y":72.4,"z":"thing"}"#);
200 }
201
202 #[test]
203 fn escaping() {
204 let object = json! {
205 x: 78u32,
206 y: 72.4f64,
207 z: "thing\nover two lines"
208 };
209 assert_eq!(
210 object,
211 "{\"x\":78,\"y\":72.4,\"z\":\"thing\\nover two lines\"}"
212 );
213 }
214
215 #[test]
216 fn vec() {
217 let z = vec!["thing", "here"];
218 let object = json! {
219 x: 78u32,
220 y: 72.4f64,
221 z: z
222 };
223 assert_eq!(object, r#"{"x":78,"y":72.4,"z":["thing","here"]}"#);
224 }
225
226 #[test]
227 fn hash_map() {
228 let values = std::collections::HashMap::from_iter([("k1", "v1"), ("k2", "v2")]);
229 let object = json! {
230 kind: "map",
231 values: values
232 };
233 let possibles = [
235 r#"{"kind":"map","values":{"k1":"v1","k2":"v2"}}"#,
236 r#"{"kind":"map","values":{"k2":"v2","k1":"v1"}}"#,
237 ];
238 assert!(possibles.contains(&object.as_str()));
239 }
240}