Skip to main content

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 | 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        // Decode it back, resulting in a dynamic structure.
504        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        // Re-encode it
518        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        // Make a new context, this time with the regular decoder for EUInformation
524        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        // Encode the object, using the regular JsonEncodable implementation
660        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        // Decode it back, resulting in a dynamic structure.
669        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        // Make a new context, this time with the regular decoder for MyUnion
681        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}