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 => {
377 todo!("StructureWithSubtypedValues is unsupported")
378 }
379 StructureType::UnionWithSubtypedValues => {
380 todo!("UnionWithSubtypedValues is unsupported")
381 }
382 }
383 }
384}
385
386impl JsonEncodable for DynamicStructure {
387 fn encode(
388 &self,
389 stream: &mut crate::json::JsonStreamWriter<&mut dyn std::io::Write>,
390 ctx: &crate::Context<'_>,
391 ) -> crate::EncodingResult<()> {
392 let s = &self.type_def;
393 stream.begin_object()?;
394 match s.structure_type {
395 crate::StructureType::Structure => {
396 for (value, field) in self.data.iter().zip(s.fields.iter()) {
397 stream.name(&field.name)?;
398 self.json_encode_field(stream, value, field, ctx)?;
399 }
400 }
401 crate::StructureType::StructureWithOptionalFields => {
402 let mut encoding_mask = 0u32;
403 let mut optional_idx = 0;
404 for (value, field) in self.data.iter().zip(s.fields.iter()) {
405 if field.is_optional {
406 if !matches!(value, Variant::Empty) {
407 encoding_mask |= 1 << optional_idx;
408 }
409 optional_idx += 1;
410 }
411 }
412 stream.name("EncodingMask")?;
413 stream.number_value(encoding_mask)?;
414
415 for (value, field) in self.data.iter().zip(s.fields.iter()) {
416 if !field.is_optional || !matches!(value, Variant::Empty) {
417 stream.name(&field.name)?;
418 self.json_encode_field(stream, value, field, ctx)?;
419 }
420 }
421 }
422 crate::StructureType::Union => {
423 if self.discriminant != 0 {
424 stream.name("SwitchField")?;
425 stream.number_value(self.discriminant)?;
426
427 let (Some(value), Some(field)) = (
428 self.data.first(),
429 s.fields.get((self.discriminant - 1) as usize),
430 ) else {
431 return Err(Error::encoding(
432 "Discriminant was out of range of known fields",
433 ));
434 };
435 stream.name(&field.name)?;
436 self.json_encode_field(stream, value, field, ctx)?;
437 }
438 }
439
440 StructureType::StructureWithSubtypedValues => {
441 todo!("StructureWithSubtypedValues is unsupported")
442 }
443 StructureType::UnionWithSubtypedValues => {
444 todo!("UnionWithSubtypedValues is unsupported")
445 }
446 }
447 stream.end_object()?;
448
449 Ok(())
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use std::{
456 io::{Cursor, Read, Seek, Write},
457 sync::Arc,
458 };
459
460 use crate::{
461 custom::custom_struct::tests::{
462 get_custom_union, get_namespaces, MyUnion, MyUnionTypeLoader,
463 },
464 json::{JsonDecodable, JsonEncodable, JsonStreamReader, JsonStreamWriter, JsonWriter},
465 Array, ContextOwned, DataTypeDefinition, DataTypeId, DecodingOptions, EUInformation,
466 ExtensionObject, LocalizedText, NamespaceMap, NodeId, StructureDefinition, StructureField,
467 TypeLoaderCollection, Variant, VariantScalarTypeId,
468 };
469
470 use crate::custom::{
471 custom_struct::tests::{add_eu_information, make_type_tree},
472 type_tree::TypeInfo,
473 DynamicStructure, DynamicTypeLoader, EncodingIds,
474 };
475
476 #[test]
477 fn json_dynamic_struct_round_trip() {
478 let mut type_tree = make_type_tree();
479 add_eu_information(&mut type_tree);
480
481 let loader = DynamicTypeLoader::new(Arc::new(type_tree));
482 let mut loaders = TypeLoaderCollection::new_empty();
483 loaders.add_type_loader(loader);
484 let ctx = ContextOwned::new(NamespaceMap::new(), loaders, DecodingOptions::test());
485
486 let mut write_buf = Vec::<u8>::new();
487 let mut cursor = Cursor::new(&mut write_buf);
488 let mut writer = JsonStreamWriter::new(&mut cursor as &mut dyn Write);
489
490 let obj = ExtensionObject::from_message(EUInformation {
491 namespace_uri: "my.namespace.uri".into(),
492 unit_id: 5,
493 display_name: "Degrees Celsius".into(),
494 description: "Description".into(),
495 });
496
497 JsonEncodable::encode(&obj, &mut writer, &ctx.context()).unwrap();
498 writer.finish_document().unwrap();
499 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
500
501 let mut reader = JsonStreamReader::new(&mut cursor as &mut dyn Read);
502
503 let obj2: ExtensionObject = JsonDecodable::decode(&mut reader, &ctx.context()).unwrap();
504
505 let value = obj2.inner_as::<DynamicStructure>().unwrap();
507 assert_eq!(value.data.len(), 4);
508 assert_eq!(value.data[0], Variant::from("my.namespace.uri"));
509 assert_eq!(value.data[1], Variant::from(5i32));
510 assert_eq!(
511 value.data[2],
512 Variant::from(LocalizedText::from("Degrees Celsius"))
513 );
514 assert_eq!(
515 value.data[3],
516 Variant::from(LocalizedText::from("Description"))
517 );
518
519 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
521 let mut writer = JsonStreamWriter::new(&mut cursor as &mut dyn Write);
522 JsonEncodable::encode(&obj2, &mut writer, &ctx.context()).unwrap();
523 writer.finish_document().unwrap();
524
525 let ctx = ContextOwned::new_default(NamespaceMap::new(), DecodingOptions::test());
527 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
528 let mut reader = JsonStreamReader::new(&mut cursor as &mut dyn Read);
529 let obj3: ExtensionObject = JsonDecodable::decode(&mut reader, &ctx.context()).unwrap();
530
531 assert_eq!(obj, obj3);
532 }
533
534 #[test]
535 fn json_dynamic_nested_struct_round_trip() {
536 let mut type_tree = make_type_tree();
537 add_eu_information(&mut type_tree);
538 let type_node_id = NodeId::new(1, 5);
539 type_tree
540 .parent_ids_mut()
541 .add_type(type_node_id.clone(), DataTypeId::Structure.into());
542 type_tree.add_type(
543 type_node_id.clone(),
544 TypeInfo::from_type_definition(
545 DataTypeDefinition::Structure(StructureDefinition {
546 default_encoding_id: NodeId::null(),
547 base_data_type: DataTypeId::Structure.into(),
548 structure_type: crate::StructureType::Structure,
549 fields: Some(vec![
550 StructureField {
551 name: "Info".into(),
552 data_type: DataTypeId::EUInformation.into(),
553 value_rank: -1,
554 ..Default::default()
555 },
556 StructureField {
557 name: "InfoArray".into(),
558 data_type: DataTypeId::EUInformation.into(),
559 value_rank: 1,
560 ..Default::default()
561 },
562 StructureField {
563 name: "AbstractField".into(),
564 data_type: DataTypeId::BaseDataType.into(),
565 value_rank: -1,
566 ..Default::default()
567 },
568 StructureField {
569 name: "PrimitiveArray".into(),
570 data_type: DataTypeId::Int32.into(),
571 value_rank: 2,
572 ..Default::default()
573 },
574 ]),
575 }),
576 "EUInformation".to_owned(),
577 Some(EncodingIds {
578 binary_id: NodeId::new(1, 6),
579 json_id: NodeId::new(1, 7),
580 xml_id: NodeId::new(1, 8),
581 }),
582 false,
583 &type_node_id,
584 type_tree.parent_ids(),
585 )
586 .unwrap(),
587 );
588 let type_tree = Arc::new(type_tree);
589 let loader = DynamicTypeLoader::new(type_tree.clone());
590 let mut loaders = TypeLoaderCollection::new();
591 loaders.add_type_loader(loader);
592 let ctx = ContextOwned::new(NamespaceMap::new(), loaders, DecodingOptions::test());
593
594 let obj = DynamicStructure::new_struct(
595 type_tree.get_struct_type(&type_node_id).unwrap().clone(),
596 type_tree,
597 vec![
598 Variant::from(ExtensionObject::from_message(EUInformation {
599 namespace_uri: "my.namespace.uri".into(),
600 unit_id: 5,
601 display_name: "Degrees Celsius".into(),
602 description: "Description".into(),
603 })),
604 Variant::from(vec![
605 ExtensionObject::from_message(EUInformation {
606 namespace_uri: "my.namespace.uri".into(),
607 unit_id: 5,
608 display_name: "Degrees Celsius".into(),
609 description: "Description".into(),
610 }),
611 ExtensionObject::from_message(EUInformation {
612 namespace_uri: "my.namespace.uri.2".into(),
613 unit_id: 6,
614 display_name: "Degrees Celsius 2".into(),
615 description: "Description 2".into(),
616 }),
617 ]),
618 Variant::Variant(Box::new(Variant::from(123))),
619 Variant::from(
620 Array::new_multi(
621 VariantScalarTypeId::Int32,
622 [1i32, 2, 3, 4, 5, 6]
623 .into_iter()
624 .map(Variant::from)
625 .collect::<Vec<_>>(),
626 vec![2, 3],
627 )
628 .unwrap(),
629 ),
630 ],
631 )
632 .unwrap();
633 let obj = ExtensionObject::from_message(obj);
634
635 let mut write_buf = Vec::<u8>::new();
636 let mut cursor = Cursor::new(&mut write_buf);
637 let mut writer = JsonStreamWriter::new(&mut cursor as &mut dyn Write);
638
639 JsonEncodable::encode(&obj, &mut writer, &ctx.context()).unwrap();
640 writer.finish_document().unwrap();
641
642 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
643
644 let mut reader = JsonStreamReader::new(&mut cursor as &mut dyn Read);
645 let obj2: ExtensionObject = JsonDecodable::decode(&mut reader, &ctx.context()).unwrap();
646
647 assert_eq!(obj, obj2);
648 }
649
650 #[test]
651 fn union_round_trip() {
652 let ctx = get_custom_union();
653
654 let mut write_buf = Vec::<u8>::new();
655 let mut cursor = Cursor::new(&mut write_buf);
656
657 let obj = ExtensionObject::from_message(MyUnion::Integer(123));
658
659 let mut writer = JsonStreamWriter::new(&mut cursor as &mut dyn Write);
660
661 JsonEncodable::encode(&obj, &mut writer, &ctx.context()).unwrap();
663 writer.finish_document().unwrap();
664 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
665
666 let mut reader = JsonStreamReader::new(&mut cursor as &mut dyn Read);
667
668 let obj2: ExtensionObject = JsonDecodable::decode(&mut reader, &ctx.context()).unwrap();
669
670 let value = obj2.inner_as::<DynamicStructure>().unwrap();
672 assert_eq!(value.data.len(), 1);
673
674 assert_eq!(value.data[0], Variant::from(123i32));
675 assert_eq!(value.discriminant, 1);
676
677 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
678 let mut writer = JsonStreamWriter::new(&mut cursor as &mut dyn Write);
679 JsonEncodable::encode(&obj2, &mut writer, &ctx.context()).unwrap();
680 writer.finish_document().unwrap();
681
682 let mut ctx = ContextOwned::new_default(get_namespaces(), DecodingOptions::test());
684 ctx.loaders_mut().add_type_loader(MyUnionTypeLoader);
685 cursor.seek(std::io::SeekFrom::Start(0)).unwrap();
686 let mut reader = JsonStreamReader::new(&mut cursor as &mut dyn Read);
687 let obj3: ExtensionObject = JsonDecodable::decode(&mut reader, &ctx.context()).unwrap();
688
689 assert_eq!(obj, obj3);
690 }
691}