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")]
90 IrValue::Color { .. } => {
91 J::Null
93 },
94 #[cfg(feature = "osc11")]
96 IrValue::Midi { .. } => {
97 J::Null
99 },
100 }
101}
102
103fn bundle_element_from_json(j: &J) -> IrBundleElement {
105 if let J::Object(map) = j {
106 if let Some(J::String(element_type)) = map.get("type") {
107 match element_type.as_str() {
108 "message" => {
109 if let Some(data) = map.get("data") {
110 return IrBundleElement::Message(from_json(data));
111 }
112 }
113 "bundle" => {
114 if let Some(data) = map.get("data") {
115 if let IrValue::Bundle(bundle) = from_json(data) {
116 return IrBundleElement::Bundle(bundle);
117 }
118 }
119 }
120 _ => {}
121 }
122 }
123 }
124 IrBundleElement::Message(from_json(j))
126}
127
128pub fn from_json(j: &J) -> IrValue {
130 match j {
131 J::Null => IrValue::Null,
132 J::Bool(b) => IrValue::Bool(*b),
133 J::Number(n) => n.as_i64().map(IrValue::Integer)
134 .or_else(|| n.as_f64().map(IrValue::Float))
135 .unwrap_or(IrValue::Null),
136 J::String(s) => IrValue::String(s.clone().into_boxed_str()),
137 J::Array(xs) => IrValue::Array(xs.iter().map(from_json).collect()),
138 J::Object(map) => {
139 if let Some(J::String(tag)) = map.get("$type") {
140 match tag.as_str() {
141 "timestamp" => {
142 let sec = map.get("seconds").and_then(|v| v.as_i64()).unwrap_or(0);
143 let ns = map.get("nanos").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
144 IrValue::Timestamp(IrTimestamp{ seconds: sec, nanos: ns })
145 }
146 "binary" => {
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::Binary(data)
150 }
151 "ext" => {
152 let ext = map.get("ext").and_then(|v| v.as_i64()).unwrap_or(0) as i8;
153 let data = map.get("data").and_then(|v| v.as_str()).map(|s|
154 base64::engine::general_purpose::STANDARD.decode(s).unwrap_or_default()).unwrap_or_default();
155 IrValue::Ext{ type_id: ext, data }
156 }
157 "bundle" => {
158 let timetag_value = map.get("timetag").and_then(|v| v.as_u64()).unwrap_or(1);
159 let timetag = IrTimetag { value: timetag_value };
160 let elements = map.get("elements").and_then(|v| v.as_array())
161 .map(|arr| arr.iter().map(bundle_element_from_json).collect())
162 .unwrap_or_default();
163 IrValue::Bundle(IrBundle { timetag, elements })
164 }
165 _ => IrValue::Map(map.iter().map(|(k,v)| (k.clone(), from_json(v))).collect())
166 }
167 } else {
168 IrValue::Map(map.iter().map(|(k,v)| (k.clone(), from_json(v))).collect())
169 }
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use osc_ir::{IrBundle, IrTimetag};
178
179 #[test]
180 fn test_bundle_json_roundtrip() {
181 let mut bundle = IrBundle::new(IrTimetag::from_ntp(12345));
183 bundle.add_message(IrValue::from("hello"));
184 bundle.add_message(IrValue::from(42));
185
186 let mut nested_bundle = IrBundle::immediate();
187 nested_bundle.add_message(IrValue::from(true));
188 nested_bundle.add_message(IrValue::from(core::f64::consts::PI));
189
190 bundle.add_bundle(nested_bundle);
191
192 let value = IrValue::Bundle(bundle.clone());
193
194 let json = to_json(&value);
196 let decoded = from_json(&json);
197
198 assert_eq!(value, decoded);
199
200 if let IrValue::Bundle(decoded_bundle) = decoded {
202 assert_eq!(decoded_bundle.timetag.value, 12345);
203 assert_eq!(decoded_bundle.elements.len(), 3);
204
205 assert!(decoded_bundle.elements[0].is_message());
207 assert_eq!(
208 decoded_bundle.elements[0].as_message().unwrap().as_str(),
209 Some("hello")
210 );
211
212 assert!(decoded_bundle.elements[1].is_message());
214 assert_eq!(
215 decoded_bundle.elements[1].as_message().unwrap().as_integer(),
216 Some(42)
217 );
218
219 assert!(decoded_bundle.elements[2].is_bundle());
221 let nested = decoded_bundle.elements[2].as_bundle().unwrap();
222 assert!(nested.is_immediate());
223 assert_eq!(nested.elements.len(), 2);
224 } else {
225 panic!("Expected Bundle variant");
226 }
227 }
228
229 #[test]
230 fn test_deeply_nested_bundle_json() {
231 let mut root = IrBundle::immediate();
233 root.add_message(IrValue::from("root"));
234
235 let mut level1 = IrBundle::new(IrTimetag::from_ntp(1000));
236 level1.add_message(IrValue::from("level1"));
237
238 let mut level2 = IrBundle::new(IrTimetag::from_ntp(2000));
239 level2.add_message(IrValue::from("level2"));
240
241 level1.add_bundle(level2);
242 root.add_bundle(level1);
243
244 let value = IrValue::Bundle(root);
245
246 let json = to_json(&value);
248 let decoded = from_json(&json);
249 assert_eq!(value, decoded);
250 }
251}