1use std::{collections::HashMap, io::Write, sync::Arc};
2
3use crate::{
4 json::{JsonDecodable, JsonEncodable, JsonReader, JsonStreamWriter, JsonWriter},
5 Array, ByteString, Context, DataValue, DateTime, DiagnosticInfo, DynEncodable, EncodingResult,
6 Error, ExpandedNodeId, ExtensionObject, Guid, LocalizedText, NodeId, QualifiedName, StatusCode,
7 StructureType, UAString, Variant, XmlElement,
8};
9
10use super::{
11 custom_struct::{DynamicStructure, DynamicTypeLoader},
12 type_tree::{ParsedStructureField, StructTypeInfo},
13};
14
15impl DynamicStructure {
16 fn json_encode_array(
17 &self,
18 stream: &mut JsonStreamWriter<&mut dyn Write>,
19 field: &ParsedStructureField,
20 ctx: &Context<'_>,
21 items: &[Variant],
22 remaining_dims: &[u32],
23 index: &mut usize,
24 ) -> EncodingResult<()> {
25 if remaining_dims.len() == 1 {
26 stream.begin_array()?;
27 for _ in 0..remaining_dims[0] {
28 self.json_encode_field(
29 stream,
30 items.get(*index).unwrap_or(&Variant::Empty),
31 field,
32 ctx,
33 )?;
34 *index += 1;
35 }
36 stream.end_array()?;
37 } else {
38 stream.begin_array()?;
39 for _ in 0..remaining_dims[0] {
40 self.json_encode_array(stream, field, ctx, items, &remaining_dims[1..], index)?;
41 }
42 stream.end_array()?;
43 }
44
45 Ok(())
46 }
47
48 fn json_encode_field(
49 &self,
50 stream: &mut JsonStreamWriter<&mut dyn Write>,
51 f: &Variant,
52 field: &ParsedStructureField,
53 ctx: &Context<'_>,
54 ) -> EncodingResult<()> {
55 match f {
56 Variant::ExtensionObject(o) => {
57 let Some(field_ty) = self.type_tree.get_struct_type(&field.type_id) else {
58 return Err(Error::encoding(format!(
59 "Dynamic type field missing from type tree: {}",
60 field.type_id
61 )));
62 };
63 if field_ty.is_abstract {
64 o.encode(stream, ctx)
65 } else {
66 let Some(body) = &o.body else {
67 return Err(Error::encoding(
68 "Dynamic type field is missing extension object body",
69 ));
70 };
71 body.encode_json(stream, ctx)
72 }
73 }
74 Variant::Array(a) => {
75 if field.value_rank > 1 {
76 let Some(dims) = &a.dimensions else {
77 return Err(Error::encoding(
78 "ArrayDimensions are required for fields with value rank > 1",
79 ));
80 };
81 if dims.len() as i32 != field.value_rank {
82 return Err(Error::encoding(
83 "ArrayDimensions must have length equal to field valuerank",
84 ));
85 }
86 let mut index = 0;
87 self.json_encode_array(stream, field, ctx, &a.values, dims, &mut index)?;
88 } else {
89 stream.begin_array()?;
90 for value in a.values.iter() {
91 self.json_encode_field(stream, value, field, ctx)?;
92 }
93 stream.end_array()?;
94 }
95
96 Ok(())
97 }
98 r => r.serialize_variant_value(stream, ctx),
99 }
100 }
101}
102
103impl DynamicTypeLoader {
104 fn json_decode_field_value(
105 &self,
106 field: &ParsedStructureField,
107 stream: &mut crate::json::JsonStreamReader<&mut dyn std::io::Read>,
108 ctx: &crate::Context<'_>,
109 ) -> EncodingResult<Variant> {
110 match field.scalar_type {
111 crate::VariantScalarTypeId::Boolean => {
112 Ok(Variant::from(<bool as JsonDecodable>::decode(stream, ctx)?))
113 }
114 crate::VariantScalarTypeId::SByte => {
115 Ok(Variant::from(<i8 as JsonDecodable>::decode(stream, ctx)?))
116 }
117 crate::VariantScalarTypeId::Byte => {
118 Ok(Variant::from(<u8 as JsonDecodable>::decode(stream, ctx)?))
119 }
120 crate::VariantScalarTypeId::Int16 => {
121 Ok(Variant::from(<i16 as JsonDecodable>::decode(stream, ctx)?))
122 }
123 crate::VariantScalarTypeId::UInt16 => {
124 Ok(Variant::from(<u16 as JsonDecodable>::decode(stream, ctx)?))
125 }
126 crate::VariantScalarTypeId::Int32 => {
127 Ok(Variant::from(<i32 as JsonDecodable>::decode(stream, ctx)?))
128 }
129 crate::VariantScalarTypeId::UInt32 => {
130 Ok(Variant::from(<u32 as JsonDecodable>::decode(stream, ctx)?))
131 }
132 crate::VariantScalarTypeId::Int64 => {
133 Ok(Variant::from(<i64 as JsonDecodable>::decode(stream, ctx)?))
134 }
135 crate::VariantScalarTypeId::UInt64 => {
136 Ok(Variant::from(<u64 as JsonDecodable>::decode(stream, ctx)?))
137 }
138 crate::VariantScalarTypeId::Float => {
139 Ok(Variant::from(<f32 as JsonDecodable>::decode(stream, ctx)?))
140 }
141 crate::VariantScalarTypeId::Double => {
142 Ok(Variant::from(<f64 as JsonDecodable>::decode(stream, ctx)?))
143 }
144 crate::VariantScalarTypeId::String => Ok(Variant::from(
145 <UAString as JsonDecodable>::decode(stream, ctx)?,
146 )),
147 crate::VariantScalarTypeId::DateTime => Ok(Variant::from(
148 <DateTime as JsonDecodable>::decode(stream, ctx)?,
149 )),
150 crate::VariantScalarTypeId::Guid => {
151 Ok(Variant::from(<Guid as JsonDecodable>::decode(stream, ctx)?))
152 }
153 crate::VariantScalarTypeId::ByteString => Ok(Variant::from(
154 <ByteString as JsonDecodable>::decode(stream, ctx)?,
155 )),
156 crate::VariantScalarTypeId::XmlElement => Ok(Variant::from(
157 <XmlElement as JsonDecodable>::decode(stream, ctx)?,
158 )),
159 crate::VariantScalarTypeId::NodeId => Ok(Variant::from(
160 <NodeId as JsonDecodable>::decode(stream, ctx)?,
161 )),
162 crate::VariantScalarTypeId::ExpandedNodeId => Ok(Variant::from(
163 <ExpandedNodeId as JsonDecodable>::decode(stream, ctx)?,
164 )),
165 crate::VariantScalarTypeId::StatusCode => Ok(Variant::from(
166 <StatusCode as JsonDecodable>::decode(stream, ctx)?,
167 )),
168 crate::VariantScalarTypeId::QualifiedName => Ok(Variant::from(
169 <QualifiedName as JsonDecodable>::decode(stream, ctx)?,
170 )),
171 crate::VariantScalarTypeId::LocalizedText => Ok(Variant::from(
172 <LocalizedText as JsonDecodable>::decode(stream, ctx)?,
173 )),
174 crate::VariantScalarTypeId::ExtensionObject => {
175 let Some(field_ty) = self.type_tree.get_struct_type(&field.type_id) else {
176 return Err(Error::decoding(format!(
177 "Dynamic type field missing from type tree: {}",
178 field.type_id
179 )));
180 };
181
182 if field_ty.is_abstract {
183 Ok(Variant::from(<ExtensionObject as JsonDecodable>::decode(
184 stream, ctx,
185 )?))
186 } else {
187 Ok(Variant::from(
188 ctx.load_from_json(&field_ty.node_id, stream)?,
189 ))
190 }
191 }
192 crate::VariantScalarTypeId::DataValue => Ok(Variant::from(
193 <DataValue as JsonDecodable>::decode(stream, ctx)?,
194 )),
195 crate::VariantScalarTypeId::Variant => Ok(Variant::Variant(Box::new(
196 <Variant as JsonDecodable>::decode(stream, ctx)?,
197 ))),
198 crate::VariantScalarTypeId::DiagnosticInfo => Ok(Variant::from(
199 <DiagnosticInfo as JsonDecodable>::decode(stream, ctx)?,
200 )),
201 }
202 }
203
204 #[allow(clippy::too_many_arguments)]
205 fn json_decode_array(
206 &self,
207 field: &ParsedStructureField,
208 stream: &mut crate::json::JsonStreamReader<&mut dyn std::io::Read>,
209 ctx: &Context<'_>,
210 value_rank: i32,
211 depth: i32,
212 values: &mut Vec<Variant>,
213 dims: &mut Vec<u32>,
214 ) -> EncodingResult<()> {
215 let mut size = 0;
216 stream.begin_array()?;
217 if value_rank > depth {
218 while stream.has_next()? {
219 size += 1;
220 self.json_decode_array(field, stream, ctx, value_rank, depth + 1, values, dims)?;
221 }
222 } else {
223 while stream.has_next()? {
224 size += 1;
225 values.push(self.json_decode_field_value(field, stream, ctx)?);
226 }
227 }
228 let old_dim = dims[depth as usize - 1];
229 if old_dim > 0 && size != old_dim {
230 return Err(Error::decoding(format!(
231 "JSON matrix in field {} does not have even dimensions",
232 field.name
233 )));
234 } else if old_dim == 0 {
235 dims[depth as usize - 1] = size;
236 }
237 stream.end_array()?;
238
239 Ok(())
240 }
241
242 fn json_decode_field(
243 &self,
244 field: &ParsedStructureField,
245 stream: &mut crate::json::JsonStreamReader<&mut dyn std::io::Read>,
246 ctx: &Context<'_>,
247 ) -> EncodingResult<Variant> {
248 if field.value_rank > 0 {
249 let mut values = Vec::new();
250 let mut dims = vec![0u32; field.value_rank as usize];
251 self.json_decode_array(
252 field,
253 stream,
254 ctx,
255 field.value_rank,
256 1,
257 &mut values,
258 &mut dims,
259 )?;
260
261 if dims.len() > 1 {
262 Ok(Variant::Array(Box::new(
263 Array::new_multi(field.scalar_type, values, dims).map_err(Error::decoding)?,
264 )))
265 } else {
266 Ok(Variant::Array(Box::new(
267 Array::new(field.scalar_type, values).map_err(Error::decoding)?,
268 )))
269 }
270 } else {
271 self.json_decode_field_value(field, stream, ctx)
272 }
273 }
274
275 pub(super) fn json_decode_type_inner(
276 &self,
277 stream: &mut crate::json::JsonStreamReader<&mut dyn std::io::Read>,
278 ctx: &Context<'_>,
279 t: &Arc<StructTypeInfo>,
280 ) -> EncodingResult<Box<dyn DynEncodable>> {
281 match t.structure_type {
282 crate::StructureType::Structure | crate::StructureType::StructureWithOptionalFields => {
283 let mut by_name = HashMap::new();
284 stream.begin_object()?;
285 while stream.has_next()? {
286 let name = stream.next_name()?;
287 let Some(field) = t.get_field_by_name(name) else {
288 stream.skip_value()?;
289 continue;
290 };
291 by_name.insert(
292 field.name.as_str(),
293 self.json_decode_field(field, stream, ctx)?,
294 );
295 }
296 let mut data = Vec::with_capacity(by_name.len());
297 for field in &t.fields {
298 let Some(f) = by_name.remove(field.name.as_str()) else {
299 if field.is_optional {
302 data.push(Variant::Empty);
303 continue;
304 }
305 return Err(Error::decoding(format!(
306 "Missing required field {}",
307 field.name
308 )));
309 };
310 data.push(f);
311 }
312 stream.end_object()?;
313
314 Ok(Box::new(DynamicStructure {
315 type_def: t.clone(),
316 discriminant: 0,
317 type_tree: self.type_tree.clone(),
318 data,
319 }))
320 }
321 crate::StructureType::Union => {
322 let mut value: Option<Variant> = None;
323 let mut discriminant: Option<u32> = None;
324
325 stream.begin_object()?;
326 while stream.has_next()? {
327 let name = stream.next_name()?;
328 match name {
329 "SwitchField" => {
330 discriminant = Some(stream.next_number()??);
331 }
332 r => {
333 let Some((idx, value_field)) =
334 t.fields.iter().enumerate().find(|(_, f)| f.name == r)
335 else {
336 stream.skip_value()?;
337 continue;
338 };
339 if discriminant.is_some_and(|d| d != (idx + 1) as u32) {
345 stream.skip_value()?;
346 } else {
347 value = Some(self.json_decode_field(value_field, stream, ctx)?);
348 discriminant = Some((idx + 1) as u32);
349 }
350 }
351 }
352 }
353
354 let (Some(value), Some(discriminant)) = (value, discriminant) else {
355 return Ok(Box::new(DynamicStructure::new_null_union(
356 t.clone(),
357 self.type_tree.clone(),
358 )));
359 };
360
361 if discriminant == 0 {
362 return Ok(Box::new(DynamicStructure::new_null_union(
363 t.clone(),
364 self.type_tree.clone(),
365 )));
366 }
367
368 Ok(Box::new(DynamicStructure {
369 type_def: t.clone(),
370 discriminant,
371 type_tree: self.type_tree.clone(),
372 data: vec![value],
373 }))
374 }
375
376 StructureType::StructureWithSubtypedValues | StructureType::UnionWithSubtypedValues => {
377 Err(Error::decoding(
378 "Structures and unions with subtyped values are unsupported",
379 ))
380 }
381 }
382 }
383}
384
385impl JsonEncodable for DynamicStructure {
386 fn encode(
387 &self,
388 stream: &mut crate::json::JsonStreamWriter<&mut dyn std::io::Write>,
389 ctx: &crate::Context<'_>,
390 ) -> crate::EncodingResult<()> {
391 let s = &self.type_def;
392 stream.begin_object()?;
393 match s.structure_type {
394 crate::StructureType::Structure => {
395 for (value, field) in self.data.iter().zip(s.fields.iter()) {
396 stream.name(&field.name)?;
397 self.json_encode_field(stream, value, field, ctx)?;
398 }
399 }
400 crate::StructureType::StructureWithOptionalFields => {
401 let mut encoding_mask = 0u32;
402 let mut optional_idx = 0;
403 for (value, field) in self.data.iter().zip(s.fields.iter()) {
404 if field.is_optional {
405 if !matches!(value, Variant::Empty) {
406 encoding_mask |= 1 << optional_idx;
407 }
408 optional_idx += 1;
409 }
410 }
411 stream.name("EncodingMask")?;
412 stream.number_value(encoding_mask)?;
413
414 for (value, field) in self.data.iter().zip(s.fields.iter()) {
415 if !field.is_optional || !matches!(value, Variant::Empty) {
416 stream.name(&field.name)?;
417 self.json_encode_field(stream, value, field, ctx)?;
418 }
419 }
420 }
421 crate::StructureType::Union => {
422 if self.discriminant != 0 {
423 stream.name("SwitchField")?;
424 stream.number_value(self.discriminant)?;
425
426 let (Some(value), Some(field)) = (
427 self.data.first(),
428 s.fields.get((self.discriminant - 1) as usize),
429 ) else {
430 return Err(Error::encoding(
431 "Discriminant was out of range of known fields",
432 ));
433 };
434 stream.name(&field.name)?;
435 self.json_encode_field(stream, value, field, ctx)?;
436 }
437 }
438
439 StructureType::StructureWithSubtypedValues | StructureType::UnionWithSubtypedValues => {
440 return Err(Error::encoding(
441 "Structures and unions with subtyped values are unsupported",
442 ));
443 }
444 }
445 stream.end_object()?;
446
447 Ok(())
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use std::{
454 io::{Cursor, Read, Seek, Write},
455 sync::Arc,
456 };
457
458 use crate::{
459 custom::custom_struct::tests::{
460 get_custom_union, get_namespaces, MyUnion, MyUnionTypeLoader,
461 },
462 json::{JsonDecodable, JsonEncodable, JsonStreamReader, JsonStreamWriter, JsonWriter},
463 Array, ContextOwned, DataTypeDefinition, DataTypeId, DecodingOptions, EUInformation,
464 ExtensionObject, LocalizedText, NamespaceMap, NodeId, StructureDefinition, StructureField,
465 TypeLoaderCollection, Variant, VariantScalarTypeId,
466 };
467
468 use crate::custom::{
469 custom_struct::tests::{add_eu_information, make_type_tree},
470 type_tree::TypeInfo,
471 DynamicStructure, DynamicTypeLoader, EncodingIds,
472 };
473
474 #[test]
475 fn json_dynamic_struct_round_trip() {
476 let mut type_tree = make_type_tree();
477 add_eu_information(&mut type_tree);
478
479 let loader = DynamicTypeLoader::new(Arc::new(type_tree));
480 let mut loaders = TypeLoaderCollection::new_empty();
481 loaders.add_type_loader(loader);
482 let ctx = ContextOwned::new(NamespaceMap::new(), loaders, DecodingOptions::test());
483
484 let mut write_buf = Vec::<u8>::new();
485 let mut cursor = Cursor::new(&mut write_buf);
486 let mut writer = JsonStreamWriter::new(&mut cursor as &mut dyn Write);
487
488 let obj = ExtensionObject::from_message(EUInformation {
489 namespace_uri: "my.namespace.uri".into(),
490 unit_id: 5,
491 display_name: "Degrees Celsius".into(),
492 description: "Description".into(),
493 });
494
495 JsonEncodable::encode(&obj, &mut writer, &ctx.context()).unwrap();
496 writer.finish_document().unwrap();
497 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
498
499 let mut reader = JsonStreamReader::new(&mut cursor as &mut dyn Read);
500
501 let obj2: ExtensionObject = JsonDecodable::decode(&mut reader, &ctx.context()).unwrap();
502
503 let value = obj2.inner_as::<DynamicStructure>().unwrap();
505 assert_eq!(value.data.len(), 4);
506 assert_eq!(value.data[0], Variant::from("my.namespace.uri"));
507 assert_eq!(value.data[1], Variant::from(5i32));
508 assert_eq!(
509 value.data[2],
510 Variant::from(LocalizedText::from("Degrees Celsius"))
511 );
512 assert_eq!(
513 value.data[3],
514 Variant::from(LocalizedText::from("Description"))
515 );
516
517 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
519 let mut writer = JsonStreamWriter::new(&mut cursor as &mut dyn Write);
520 JsonEncodable::encode(&obj2, &mut writer, &ctx.context()).unwrap();
521 writer.finish_document().unwrap();
522
523 let ctx = ContextOwned::new_default(NamespaceMap::new(), DecodingOptions::test());
525 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
526 let mut reader = JsonStreamReader::new(&mut cursor as &mut dyn Read);
527 let obj3: ExtensionObject = JsonDecodable::decode(&mut reader, &ctx.context()).unwrap();
528
529 assert_eq!(obj, obj3);
530 }
531
532 #[test]
533 fn json_dynamic_nested_struct_round_trip() {
534 let mut type_tree = make_type_tree();
535 add_eu_information(&mut type_tree);
536 let type_node_id = NodeId::new(1, 5);
537 type_tree
538 .parent_ids_mut()
539 .add_type(type_node_id.clone(), DataTypeId::Structure.into());
540 type_tree.add_type(
541 type_node_id.clone(),
542 TypeInfo::from_type_definition(
543 DataTypeDefinition::Structure(StructureDefinition {
544 default_encoding_id: NodeId::null(),
545 base_data_type: DataTypeId::Structure.into(),
546 structure_type: crate::StructureType::Structure,
547 fields: Some(vec![
548 StructureField {
549 name: "Info".into(),
550 data_type: DataTypeId::EUInformation.into(),
551 value_rank: -1,
552 ..Default::default()
553 },
554 StructureField {
555 name: "InfoArray".into(),
556 data_type: DataTypeId::EUInformation.into(),
557 value_rank: 1,
558 ..Default::default()
559 },
560 StructureField {
561 name: "AbstractField".into(),
562 data_type: DataTypeId::BaseDataType.into(),
563 value_rank: -1,
564 ..Default::default()
565 },
566 StructureField {
567 name: "PrimitiveArray".into(),
568 data_type: DataTypeId::Int32.into(),
569 value_rank: 2,
570 ..Default::default()
571 },
572 ]),
573 }),
574 "EUInformation".to_owned(),
575 Some(EncodingIds {
576 binary_id: NodeId::new(1, 6),
577 json_id: NodeId::new(1, 7),
578 xml_id: NodeId::new(1, 8),
579 }),
580 false,
581 &type_node_id,
582 type_tree.parent_ids(),
583 )
584 .unwrap(),
585 );
586 let type_tree = Arc::new(type_tree);
587 let loader = DynamicTypeLoader::new(type_tree.clone());
588 let mut loaders = TypeLoaderCollection::new();
589 loaders.add_type_loader(loader);
590 let ctx = ContextOwned::new(NamespaceMap::new(), loaders, DecodingOptions::test());
591
592 let obj = DynamicStructure::new_struct(
593 type_tree.get_struct_type(&type_node_id).unwrap().clone(),
594 type_tree,
595 vec![
596 Variant::from(ExtensionObject::from_message(EUInformation {
597 namespace_uri: "my.namespace.uri".into(),
598 unit_id: 5,
599 display_name: "Degrees Celsius".into(),
600 description: "Description".into(),
601 })),
602 Variant::from(vec![
603 ExtensionObject::from_message(EUInformation {
604 namespace_uri: "my.namespace.uri".into(),
605 unit_id: 5,
606 display_name: "Degrees Celsius".into(),
607 description: "Description".into(),
608 }),
609 ExtensionObject::from_message(EUInformation {
610 namespace_uri: "my.namespace.uri.2".into(),
611 unit_id: 6,
612 display_name: "Degrees Celsius 2".into(),
613 description: "Description 2".into(),
614 }),
615 ]),
616 Variant::Variant(Box::new(Variant::from(123))),
617 Variant::from(
618 Array::new_multi(
619 VariantScalarTypeId::Int32,
620 [1i32, 2, 3, 4, 5, 6]
621 .into_iter()
622 .map(Variant::from)
623 .collect::<Vec<_>>(),
624 vec![2, 3],
625 )
626 .unwrap(),
627 ),
628 ],
629 )
630 .unwrap();
631 let obj = ExtensionObject::from_message(obj);
632
633 let mut write_buf = Vec::<u8>::new();
634 let mut cursor = Cursor::new(&mut write_buf);
635 let mut writer = JsonStreamWriter::new(&mut cursor as &mut dyn Write);
636
637 JsonEncodable::encode(&obj, &mut writer, &ctx.context()).unwrap();
638 writer.finish_document().unwrap();
639
640 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
641
642 let mut reader = JsonStreamReader::new(&mut cursor as &mut dyn Read);
643 let obj2: ExtensionObject = JsonDecodable::decode(&mut reader, &ctx.context()).unwrap();
644
645 assert_eq!(obj, obj2);
646 }
647
648 #[test]
649 fn union_round_trip() {
650 let ctx = get_custom_union();
651
652 let mut write_buf = Vec::<u8>::new();
653 let mut cursor = Cursor::new(&mut write_buf);
654
655 let obj = ExtensionObject::from_message(MyUnion::Integer(123));
656
657 let mut writer = JsonStreamWriter::new(&mut cursor as &mut dyn Write);
658
659 JsonEncodable::encode(&obj, &mut writer, &ctx.context()).unwrap();
661 writer.finish_document().unwrap();
662 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
663
664 let mut reader = JsonStreamReader::new(&mut cursor as &mut dyn Read);
665
666 let obj2: ExtensionObject = JsonDecodable::decode(&mut reader, &ctx.context()).unwrap();
667
668 let value = obj2.inner_as::<DynamicStructure>().unwrap();
670 assert_eq!(value.data.len(), 1);
671
672 assert_eq!(value.data[0], Variant::from(123i32));
673 assert_eq!(value.discriminant, 1);
674
675 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
676 let mut writer = JsonStreamWriter::new(&mut cursor as &mut dyn Write);
677 JsonEncodable::encode(&obj2, &mut writer, &ctx.context()).unwrap();
678 writer.finish_document().unwrap();
679
680 let mut ctx = ContextOwned::new_default(get_namespaces(), DecodingOptions::test());
682 ctx.loaders_mut().add_type_loader(MyUnionTypeLoader);
683 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
684 let mut reader = JsonStreamReader::new(&mut cursor as &mut dyn Read);
685 let obj3: ExtensionObject = JsonDecodable::decode(&mut reader, &ctx.context()).unwrap();
686
687 assert_eq!(obj, obj3);
688 }
689}