opcua_types/custom/
json.rs

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                        // Just ignore decoding mask here, there really is no reason
300                        // to care about it when it comes to JSON decoding.
301                        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 we've read the discriminant, double check that it matches the field name.
340                            // OPC-UA unions are really only allowed to have two fields, but we can try to handle
341                            // weird payloads anyway.
342                            // Technically doesn't handle cases where there are multiple options _and_ the discriminant
343                            // is late, but that violates the standard so it's probably fine.
344                            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        // Decode it back, resulting in a dynamic structure.
506        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        // Re-encode it
520        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        // Make a new context, this time with the regular decoder for EUInformation
526        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        // Encode the object, using the regular JsonEncodable implementation
662        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        // Decode it back, resulting in a dynamic structure.
671        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        // Make a new context, this time with the regular decoder for MyUnion
683        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}