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