1use osc_ir::{IrValue, IrTimestamp, IrBundle, IrBundleElement, IrTimetag};
42use serde_json::Value as J;
43use base64::Engine;
44
45fn bundle_element_to_json(element: &IrBundleElement) -> J {
47 match element {
48 IrBundleElement::Message(msg) => J::Object([
49 ("type".to_string(), J::from("message")),
50 ("data".to_string(), to_json(msg)),
51 ].into_iter().collect()),
52 IrBundleElement::Bundle(bundle) => J::Object([
53 ("type".to_string(), J::from("bundle")),
54 ("data".to_string(), to_json(&IrValue::Bundle(bundle.clone()))),
55 ].into_iter().collect()),
56 }
57}
58
59pub fn to_json(v: &IrValue) -> J {
61 match v {
62 IrValue::Null => J::Null,
63 IrValue::Bool(b) => J::Bool(*b),
64 IrValue::Integer(i) => J::from(*i),
65 IrValue::Float(x) => J::from(*x),
66 IrValue::String(s) => J::from(s.as_ref()),
67 IrValue::Binary(bytes) => J::Object([
68 ("$type".to_string(), J::from("binary")),
69 ("data".to_string(), J::from(base64::engine::general_purpose::STANDARD.encode(bytes))),
70 ].into_iter().collect()),
71 IrValue::Array(xs) => J::Array(xs.iter().map(to_json).collect()),
72 IrValue::Map(entries) => J::Object(entries.iter().map(|(k, v)| (k.clone(), to_json(v))).collect()),
73 IrValue::Timestamp(IrTimestamp{seconds, nanos}) => J::Object([
74 ("$type".to_string(), J::from("timestamp")),
75 ("seconds".to_string(), J::from(*seconds)),
76 ("nanos".to_string(), J::from(*nanos as u64)),
77 ].into_iter().collect()),
78 IrValue::Ext{ type_id, data } => J::Object([
79 ("$type".to_string(), J::from("ext")),
80 ("ext".to_string(), J::from(*type_id as i64)),
81 ("data".to_string(), J::from(base64::engine::general_purpose::STANDARD.encode(data))),
82 ].into_iter().collect()),
83 IrValue::Bundle(bundle) => J::Object([
84 ("$type".to_string(), J::from("bundle")),
85 ("timetag".to_string(), J::from(bundle.timetag.value)),
86 ("elements".to_string(), J::Array(bundle.elements.iter().map(bundle_element_to_json).collect())),
87 ].into_iter().collect()),
88 #[cfg(feature = "osc11")]
91 IrValue::Color { .. } => J::Null,
92 #[cfg(feature = "osc11")]
93 IrValue::Midi { .. } => J::Null,
94 }
95}
96
97fn bundle_element_from_json(j: &J) -> IrBundleElement {
99 if let J::Object(map) = j {
100 if let Some(J::String(element_type)) = map.get("type") {
101 match element_type.as_str() {
102 "message" => {
103 if let Some(data) = map.get("data") {
104 return IrBundleElement::Message(from_json(data));
105 }
106 }
107 "bundle" => {
108 if let Some(data) = map.get("data") {
109 if let IrValue::Bundle(bundle) = from_json(data) {
110 return IrBundleElement::Bundle(bundle);
111 }
112 }
113 }
114 _ => {}
115 }
116 }
117 }
118 IrBundleElement::Message(from_json(j))
120}
121
122pub fn from_json(j: &J) -> IrValue {
124 match j {
125 J::Null => IrValue::Null,
126 J::Bool(b) => IrValue::Bool(*b),
127 J::Number(n) => n.as_i64().map(IrValue::Integer)
128 .or_else(|| n.as_f64().map(IrValue::Float))
129 .unwrap_or(IrValue::Null),
130 J::String(s) => IrValue::String(s.clone().into_boxed_str()),
131 J::Array(xs) => IrValue::Array(xs.iter().map(from_json).collect()),
132 J::Object(map) => {
133 if let Some(J::String(tag)) = map.get("$type") {
134 match tag.as_str() {
135 "timestamp" => {
136 let sec = map.get("seconds").and_then(|v| v.as_i64()).unwrap_or(0);
137 let ns = map.get("nanos").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
138 IrValue::Timestamp(IrTimestamp{ seconds: sec, nanos: ns })
139 }
140 "binary" => {
141 let data = map.get("data").and_then(|v| v.as_str()).map(|s|
142 base64::engine::general_purpose::STANDARD.decode(s).unwrap_or_default()).unwrap_or_default();
143 IrValue::Binary(data)
144 }
145 "ext" => {
146 let ext = map.get("ext").and_then(|v| v.as_i64()).unwrap_or(0) as i8;
147 let data = map.get("data").and_then(|v| v.as_str()).map(|s|
148 base64::engine::general_purpose::STANDARD.decode(s).unwrap_or_default()).unwrap_or_default();
149 IrValue::Ext{ type_id: ext, data }
150 }
151 "bundle" => {
152 let timetag_value = map.get("timetag").and_then(|v| v.as_u64()).unwrap_or(1);
153 let timetag = IrTimetag { value: timetag_value };
154 let elements = map.get("elements").and_then(|v| v.as_array())
155 .map(|arr| arr.iter().map(bundle_element_from_json).collect())
156 .unwrap_or_default();
157 IrValue::Bundle(IrBundle { timetag, elements })
158 }
159 _ => IrValue::Map(map.iter().map(|(k,v)| (k.clone(), from_json(v))).collect())
160 }
161 } else {
162 IrValue::Map(map.iter().map(|(k,v)| (k.clone(), from_json(v))).collect())
163 }
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use osc_ir::{IrBundle, IrTimetag};
172
173 #[test]
174 fn test_bundle_json_roundtrip() {
175 let mut bundle = IrBundle::new(IrTimetag::from_ntp(12345));
177 bundle.add_message(IrValue::from("hello"));
178 bundle.add_message(IrValue::from(42));
179
180 let mut nested_bundle = IrBundle::immediate();
181 nested_bundle.add_message(IrValue::from(true));
182 nested_bundle.add_message(IrValue::from(core::f64::consts::PI));
183
184 bundle.add_bundle(nested_bundle);
185
186 let value = IrValue::Bundle(bundle.clone());
187
188 let json = to_json(&value);
190 let decoded = from_json(&json);
191
192 assert_eq!(value, decoded);
193
194 if let IrValue::Bundle(decoded_bundle) = decoded {
196 assert_eq!(decoded_bundle.timetag.value, 12345);
197 assert_eq!(decoded_bundle.elements.len(), 3);
198
199 assert!(decoded_bundle.elements[0].is_message());
201 assert_eq!(
202 decoded_bundle.elements[0].as_message().unwrap().as_str(),
203 Some("hello")
204 );
205
206 assert!(decoded_bundle.elements[1].is_message());
208 assert_eq!(
209 decoded_bundle.elements[1].as_message().unwrap().as_integer(),
210 Some(42)
211 );
212
213 assert!(decoded_bundle.elements[2].is_bundle());
215 let nested = decoded_bundle.elements[2].as_bundle().unwrap();
216 assert!(nested.is_immediate());
217 assert_eq!(nested.elements.len(), 2);
218 } else {
219 panic!("Expected Bundle variant");
220 }
221 }
222
223 #[test]
224 fn test_deeply_nested_bundle_json() {
225 let mut root = IrBundle::immediate();
227 root.add_message(IrValue::from("root"));
228
229 let mut level1 = IrBundle::new(IrTimetag::from_ntp(1000));
230 level1.add_message(IrValue::from("level1"));
231
232 let mut level2 = IrBundle::new(IrTimetag::from_ntp(2000));
233 level2.add_message(IrValue::from("level2"));
234
235 level1.add_bundle(level2);
236 root.add_bundle(level1);
237
238 let value = IrValue::Bundle(root);
239
240 let json = to_json(&value);
242 let decoded = from_json(&json);
243 assert_eq!(value, decoded);
244 }
245}