Skip to main content

parquet_variant_compute/
variant_array_builder.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership
3// SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements
4
5//! [`VariantArrayBuilder`] implementation
6
7use crate::VariantArray;
8use arrow::array::builder::make_view;
9use arrow::array::{ArrayRef, BinaryViewArray, NullBufferBuilder, StructArray};
10use arrow::buffer::Buffer;
11use arrow_schema::{ArrowError, DataType, Field, Fields};
12use parquet_variant::{
13    BuilderSpecificState, ListBuilder, MetadataBuilder, ObjectBuilder, Variant, VariantBuilderExt,
14    VariantMetadata,
15};
16use parquet_variant::{
17    ParentState, ReadOnlyMetadataBuilder, ValueBuilder, WritableMetadataBuilder,
18};
19use std::sync::Arc;
20
21/// A builder for [`VariantArray`]
22///
23/// This builder is used to construct a `VariantArray` and allows APIs for
24/// adding metadata
25///
26/// This builder always creates a `VariantArray` using [`BinaryViewArray`] for both
27/// the metadata and value fields.
28///
29/// `VariantArrayBuilder` implements [`VariantBuilderExt`], so you append values
30/// and nested objects or lists the same way as when building a single
31/// [`Variant`] value with [`VariantBuilder`], rather than constructing a
32/// `VariantBuilder` per row.
33///
34/// [`VariantBuilder`]: parquet_variant::VariantBuilder
35/// [`VariantBuilderExt`]: parquet_variant::VariantBuilderExt
36///
37/// # TODO
38/// 1. Support shredding: <https://github.com/apache/arrow-rs/issues/7895>
39///
40/// ## Example:
41/// ```
42/// # use arrow::array::Array;
43/// # use parquet_variant::{Variant, VariantBuilder, VariantBuilderExt};
44/// # use parquet_variant_compute::VariantArrayBuilder;
45/// # use parquet_variant::ShortString;
46/// // Create a new VariantArrayBuilder with a capacity of 100 rows
47/// let mut builder = VariantArrayBuilder::new(100);
48/// // append variant values
49/// builder.append_variant(Variant::from(42));
50/// // append a null row (note not a Variant::Null)
51/// builder.append_null();
52/// // append an object to the builder using VariantBuilderExt methods directly
53/// builder.new_object()
54///   .with_field("foo", "bar")
55///   .finish();
56///
57/// // bulk insert a list of values
58/// // `Option::None` is a null value
59/// builder.extend([None, Some(Variant::from("norm"))]);
60///
61/// // create the final VariantArray
62/// let variant_array = builder.build();
63/// assert_eq!(variant_array.len(), 5);
64/// // // Access the values
65/// // row 1 is not null and is an integer
66/// assert!(!variant_array.is_null(0));
67/// assert_eq!(variant_array.value(0), Variant::from(42i32));
68/// // row 1 is null
69/// assert!(variant_array.is_null(1));
70/// // row 2 is not null and is an object
71/// assert!(!variant_array.is_null(2));
72/// let value = variant_array.value(2);
73/// let obj = value.as_object().expect("expected object");
74/// assert_eq!(obj.get("foo"), Some(Variant::from("bar")));
75/// // row 3 is null
76/// assert!(variant_array.is_null(3));
77/// // row 4 is not null and is a short string
78/// assert!(!variant_array.is_null(4));
79/// let value = variant_array.value(4);
80/// assert_eq!(value, Variant::ShortString(ShortString::try_new("norm").unwrap()));
81/// ```
82#[derive(Debug)]
83pub struct VariantArrayBuilder {
84    /// Nulls
85    nulls: NullBufferBuilder,
86    /// builder for all the metadata
87    metadata_builder: WritableMetadataBuilder,
88    /// ending offset for each serialized metadata dictionary in the buffer
89    metadata_offsets: Vec<usize>,
90    /// builder for values
91    value_builder: ValueBuilder,
92    /// ending offset for each serialized variant value in the buffer
93    value_offsets: Vec<usize>,
94    /// The fields of the final `StructArray`
95    ///
96    /// TODO: 1) Add extension type metadata
97    /// TODO: 2) Add support for shredding
98    fields: Fields,
99}
100
101impl VariantArrayBuilder {
102    pub fn new(row_capacity: usize) -> Self {
103        // The subfields are expected to be non-nullable according to the parquet variant spec.
104        let metadata_field = Field::new("metadata", DataType::BinaryView, false);
105        let value_field = Field::new("value", DataType::BinaryView, false);
106
107        Self {
108            nulls: NullBufferBuilder::new(row_capacity),
109            metadata_builder: WritableMetadataBuilder::default(),
110            metadata_offsets: Vec::with_capacity(row_capacity),
111            value_builder: ValueBuilder::new(),
112            value_offsets: Vec::with_capacity(row_capacity),
113            fields: Fields::from(vec![metadata_field, value_field]),
114        }
115    }
116
117    /// Build the final builder
118    pub fn build(self) -> VariantArray {
119        let Self {
120            mut nulls,
121            metadata_builder,
122            metadata_offsets,
123            value_builder,
124            value_offsets,
125            fields,
126        } = self;
127
128        let metadata_buffer = metadata_builder.into_inner();
129        let metadata_array = binary_view_array_from_buffers(metadata_buffer, metadata_offsets);
130
131        let value_buffer = value_builder.into_inner();
132        let value_array = binary_view_array_from_buffers(value_buffer, value_offsets);
133
134        // The build the final struct array
135        let inner = StructArray::new(
136            fields,
137            vec![
138                Arc::new(metadata_array) as ArrayRef,
139                Arc::new(value_array) as ArrayRef,
140            ],
141            nulls.finish(),
142        );
143        // TODO add arrow extension type metadata
144
145        VariantArray::try_new(&inner).expect("valid VariantArray by construction")
146    }
147
148    /// Appends a null row to the builder.
149    pub fn append_null(&mut self) {
150        self.nulls.append_null();
151        // The subfields are expected to be non-nullable according to the parquet variant spec.
152        self.metadata_offsets.push(self.metadata_builder.offset());
153        self.value_offsets.push(self.value_builder.offset());
154    }
155
156    /// Appends `n` null rows to the builder.
157    pub fn append_nulls(&mut self, n: usize) {
158        self.nulls.append_n_nulls(n);
159        // The subfields are expected to be non-nullable according to the parquet variant spec.
160        let metadata_offset = self.metadata_builder.offset();
161        let value_offset = self.value_builder.offset();
162        self.metadata_offsets
163            .extend(std::iter::repeat_n(metadata_offset, n));
164        self.value_offsets
165            .extend(std::iter::repeat_n(value_offset, n));
166    }
167
168    /// Append the [`Variant`] to the builder as the next row
169    pub fn append_variant(&mut self, variant: Variant) {
170        ValueBuilder::append_variant(self.parent_state(), variant);
171    }
172
173    /// Creates a builder-specific parent state
174    fn parent_state(&mut self) -> ParentState<'_, ArrayBuilderState<'_>> {
175        let state = ArrayBuilderState {
176            metadata_offsets: &mut self.metadata_offsets,
177            value_offsets: &mut self.value_offsets,
178            nulls: &mut self.nulls,
179        };
180
181        ParentState::new(&mut self.value_builder, &mut self.metadata_builder, state)
182    }
183}
184
185impl<'m, 'v> Extend<Option<Variant<'m, 'v>>> for VariantArrayBuilder {
186    fn extend<T: IntoIterator<Item = Option<Variant<'m, 'v>>>>(&mut self, iter: T) {
187        for v in iter {
188            match v {
189                Some(v) => self.append_variant(v),
190                None => self.append_null(),
191            }
192        }
193    }
194}
195
196/// Builder-specific state for array building that manages array-level offsets and nulls. See
197/// [`VariantBuilderExt`] for details.
198#[derive(Debug)]
199pub struct ArrayBuilderState<'a> {
200    metadata_offsets: &'a mut Vec<usize>,
201    value_offsets: &'a mut Vec<usize>,
202    nulls: &'a mut NullBufferBuilder,
203}
204
205// All changes are pending until finalized
206impl BuilderSpecificState for ArrayBuilderState<'_> {
207    fn finish(
208        &mut self,
209        metadata_builder: &mut dyn MetadataBuilder,
210        value_builder: &mut ValueBuilder,
211    ) {
212        self.metadata_offsets.push(metadata_builder.finish());
213        self.value_offsets.push(value_builder.offset());
214        self.nulls.append_non_null();
215    }
216}
217
218impl VariantBuilderExt for VariantArrayBuilder {
219    type State<'a>
220        = ArrayBuilderState<'a>
221    where
222        Self: 'a;
223
224    /// Appending NULL to a variant array produces an actual NULL value
225    fn append_null(&mut self) {
226        self.append_null();
227    }
228
229    fn append_value<'m, 'v>(&mut self, value: impl Into<Variant<'m, 'v>>) {
230        self.append_variant(value.into());
231    }
232
233    fn try_new_list(&mut self) -> Result<ListBuilder<'_, Self::State<'_>>, ArrowError> {
234        Ok(ListBuilder::new(self.parent_state(), false))
235    }
236
237    fn try_new_object(&mut self) -> Result<ObjectBuilder<'_, Self::State<'_>>, ArrowError> {
238        Ok(ObjectBuilder::new(self.parent_state(), false))
239    }
240}
241
242/// A builder for creating only the value column of a [`VariantArray`]
243///
244/// This builder is used when you have existing metadata and only need to build
245/// the value column. It's useful for scenarios like variant unshredding, data
246/// transformation, or filtering where you want to reuse existing metadata.
247///
248/// The builder produces a [`BinaryViewArray`] that can be combined with existing
249/// metadata to create a complete [`VariantArray`].
250///
251/// # Example:
252/// ```
253/// # use arrow::array::Array;
254/// # use parquet_variant::{Variant};
255/// # use parquet_variant_compute::VariantValueArrayBuilder;
256/// // Create a variant value builder for 10 rows
257/// let mut builder = VariantValueArrayBuilder::new(10);
258///
259/// // Append some values with their corresponding metadata, which the
260/// // builder takes advantage of to avoid creating new metadata.
261/// builder.append_value(Variant::from(42));
262/// builder.append_null();
263/// builder.append_value(Variant::from("hello"));
264///
265/// // Build the final value array
266/// let value_array = builder.build().unwrap();
267/// assert_eq!(value_array.len(), 3);
268/// ```
269#[derive(Debug)]
270pub struct VariantValueArrayBuilder {
271    value_builder: ValueBuilder,
272    value_offsets: Vec<usize>,
273    nulls: NullBufferBuilder,
274}
275
276impl VariantValueArrayBuilder {
277    /// Create a new `VariantValueArrayBuilder` with the specified row capacity
278    pub fn new(row_capacity: usize) -> Self {
279        Self {
280            value_builder: ValueBuilder::new(),
281            value_offsets: Vec::with_capacity(row_capacity),
282            nulls: NullBufferBuilder::new(row_capacity),
283        }
284    }
285
286    /// Build the final value array
287    ///
288    /// Returns a [`BinaryViewArray`] containing the serialized variant values.
289    /// This can be combined with existing metadata to create a complete [`VariantArray`].
290    pub fn build(mut self) -> Result<BinaryViewArray, ArrowError> {
291        let value_buffer = self.value_builder.into_inner();
292        let mut array = binary_view_array_from_buffers(value_buffer, self.value_offsets);
293        if let Some(nulls) = self.nulls.finish() {
294            let (views, buffers, _) = array.into_parts();
295            array = BinaryViewArray::try_new(views, buffers, Some(nulls))?;
296        }
297        Ok(array)
298    }
299
300    /// Append a null row to the builder
301    ///
302    /// WARNING: It is only valid to call this method when building the `value` field of a shredded
303    /// variant column (which is nullable). The `value` field of a binary (unshredded) variant
304    /// column is non-nullable, and callers should instead invoke [`Self::append_value`] with
305    /// `Variant::Null`, passing the appropriate metadata value.
306    pub fn append_null(&mut self) {
307        self.value_offsets.push(self.value_builder.offset());
308        self.nulls.append_null();
309    }
310
311    /// Append a variant value with its corresponding metadata
312    ///
313    /// # Arguments
314    /// * `value` - The variant value to append
315    /// * `metadata` - The metadata dictionary for this variant (used for field name resolution)
316    ///
317    /// # Returns
318    /// * `Ok(())` if the value was successfully appended
319    /// * `Err(ArrowError)` if the variant contains field names not found in the metadata
320    ///
321    /// # Example
322    /// ```
323    /// # use parquet_variant::Variant;
324    /// # use parquet_variant_compute::VariantValueArrayBuilder;
325    /// let mut builder = VariantValueArrayBuilder::new(10);
326    /// builder.append_value(Variant::from(42));
327    /// ```
328    pub fn append_value(&mut self, value: Variant<'_, '_>) {
329        // NOTE: Have to clone because the builder consumes `value`
330        self.builder_ext(&value.metadata().clone())
331            .append_value(value);
332    }
333
334    /// Creates a builder-specific parent state.
335    ///
336    /// For example, this can be useful for code that wants to copy a subset of fields from an
337    /// object `value` as a new row of `value_array_builder`:
338    ///
339    /// ```no_run
340    /// # use parquet_variant::{ObjectBuilder, ReadOnlyMetadataBuilder, Variant};
341    /// # use parquet_variant_compute::VariantValueArrayBuilder;
342    /// # let value = Variant::Null;
343    /// # let mut value_array_builder = VariantValueArrayBuilder::new(0);
344    /// # fn should_keep(field_name: &str) -> bool { todo!() };
345    /// let Variant::Object(obj) = value else {
346    ///     panic!("Not a variant object");
347    /// };
348    /// let mut metadata_builder = ReadOnlyMetadataBuilder::new(&obj.metadata);
349    /// let state = value_array_builder.parent_state(&mut metadata_builder);
350    /// let mut object_builder = ObjectBuilder::new(state, false);
351    /// for (field_name, field_value) in obj.iter() {
352    ///     if should_keep(field_name) {
353    ///         object_builder.insert_bytes(field_name, field_value);
354    ///     }
355    /// }
356    ///  object_builder.finish(); // appends the filtered object
357    /// ```
358    pub fn parent_state<'a>(
359        &'a mut self,
360        metadata_builder: &'a mut dyn MetadataBuilder,
361    ) -> ParentState<'a, ValueArrayBuilderState<'a>> {
362        let state = ValueArrayBuilderState {
363            value_offsets: &mut self.value_offsets,
364            nulls: &mut self.nulls,
365        };
366
367        ParentState::new(&mut self.value_builder, metadata_builder, state)
368    }
369
370    /// Creates a thin [`VariantBuilderExt`] wrapper for this builder, which hides the `metadata`
371    /// parameter (similar to the way [`parquet_variant::ObjectFieldBuilder`] hides field names).
372    pub fn builder_ext<'a>(
373        &'a mut self,
374        metadata: &'a VariantMetadata<'a>,
375    ) -> VariantValueArrayBuilderExt<'a> {
376        VariantValueArrayBuilderExt {
377            metadata_builder: ReadOnlyMetadataBuilder::new(metadata),
378            value_builder: self,
379        }
380    }
381}
382
383/// Builder-specific state for array building that manages array-level offsets and nulls. See
384/// [`VariantBuilderExt`] for details.
385#[derive(Debug)]
386pub struct ValueArrayBuilderState<'a> {
387    value_offsets: &'a mut Vec<usize>,
388    nulls: &'a mut NullBufferBuilder,
389}
390
391// All changes are pending until finalized
392impl BuilderSpecificState for ValueArrayBuilderState<'_> {
393    fn finish(
394        &mut self,
395        _metadata_builder: &mut dyn MetadataBuilder,
396        value_builder: &mut ValueBuilder,
397    ) {
398        self.value_offsets.push(value_builder.offset());
399        self.nulls.append_non_null();
400    }
401}
402
403/// A thin [`VariantBuilderExt`] wrapper that hides the short-lived (per-row)
404/// [`ReadOnlyMetadataBuilder`] instances that [`VariantValueArrayBuilder`] requires.
405pub struct VariantValueArrayBuilderExt<'a> {
406    metadata_builder: ReadOnlyMetadataBuilder<'a>,
407    value_builder: &'a mut VariantValueArrayBuilder,
408}
409
410impl<'a> VariantValueArrayBuilderExt<'a> {
411    /// Creates a new instance from a metadata builder and a reference to a variant value builder.
412    pub fn new(
413        metadata_builder: ReadOnlyMetadataBuilder<'a>,
414        value_builder: &'a mut VariantValueArrayBuilder,
415    ) -> Self {
416        Self {
417            metadata_builder,
418            value_builder,
419        }
420    }
421}
422
423impl VariantBuilderExt for VariantValueArrayBuilderExt<'_> {
424    type State<'b>
425        = ValueArrayBuilderState<'b>
426    where
427        Self: 'b;
428
429    /// Appending NULL produces `Variant::Null`, because the `value` column of an unshredded variant
430    /// is non-nullable. Use [`VariantValueArrayBuilder::append_null`] for a shredded column.
431    fn append_null(&mut self) {
432        self.append_value(Variant::Null);
433    }
434
435    fn append_value<'m, 'v>(&mut self, value: impl Into<Variant<'m, 'v>>) {
436        let state = self.value_builder.parent_state(&mut self.metadata_builder);
437        ValueBuilder::append_variant_bytes(state, value.into());
438    }
439
440    fn try_new_list(&mut self) -> Result<ListBuilder<'_, Self::State<'_>>, ArrowError> {
441        let state = self.value_builder.parent_state(&mut self.metadata_builder);
442        Ok(ListBuilder::new(state, false))
443    }
444
445    fn try_new_object(&mut self) -> Result<ObjectBuilder<'_, Self::State<'_>>, ArrowError> {
446        let state = self.value_builder.parent_state(&mut self.metadata_builder);
447        Ok(ObjectBuilder::new(state, false))
448    }
449}
450
451fn binary_view_array_from_buffers(buffer: Vec<u8>, offsets: Vec<usize>) -> BinaryViewArray {
452    // Each builder records the current buffer length after appending a row, so offsets are
453    // monotonically increasing and bounded by the final buffer length.
454    assert!(buffer.len() < u32::MAX as usize);
455
456    let buffer = Buffer::from(buffer);
457    let mut views = Vec::with_capacity(offsets.len());
458    let mut start = 0_usize;
459    for end in offsets {
460        views.push(make_view(&buffer[start..end], 0, start as u32));
461        start = end;
462    }
463
464    // SAFETY: `make_view` constructs every view from an in-bounds slice of buffer 0, and there
465    // are no nulls. The buffer length check above guarantees every offset fits in a `u32`.
466    unsafe { BinaryViewArray::new_unchecked(views.into(), vec![buffer].into(), None) }
467}
468
469#[cfg(test)]
470mod test {
471    use super::*;
472    use arrow::array::Array;
473    use parquet_variant::{ShortString, Variant};
474
475    /// Test that both the metadata and value buffers are non nullable
476    #[test]
477    fn test_variant_array_builder_non_nullable() {
478        let mut builder = VariantArrayBuilder::new(10);
479
480        builder.extend([
481            None, // should not panic
482            Some(Variant::from(42_i32)),
483        ]);
484
485        let variant_array = builder.build();
486
487        assert_eq!(variant_array.len(), 2);
488        assert!(variant_array.is_null(0));
489        assert!(!variant_array.is_null(1));
490        assert_eq!(variant_array.value(1), Variant::from(42i32));
491
492        // the metadata and value fields of non shredded variants should not be null
493        assert!(variant_array.metadata_column().nulls().is_none());
494        assert!(variant_array.value_column().nulls().is_none());
495        let DataType::Struct(fields) = variant_array.data_type() else {
496            panic!("Expected VariantArray to have Struct data type");
497        };
498        for field in fields {
499            assert!(
500                !field.is_nullable(),
501                "Field {} should be non-nullable",
502                field.name()
503            );
504        }
505    }
506
507    /// Test using appending variants to the array builder
508    #[test]
509    fn test_variant_array_builder() {
510        let mut builder = VariantArrayBuilder::new(10);
511        builder.append_null(); // should not panic
512        builder.append_variant(Variant::from(42i32));
513
514        // make an object in the next row
515        builder.new_object().with_field("foo", "bar").finish();
516
517        // append a new list
518        builder
519            .new_list()
520            .with_value(Variant::from(1i32))
521            .with_value(Variant::from(2i32))
522            .finish();
523        let variant_array = builder.build();
524
525        assert_eq!(variant_array.len(), 4);
526        assert!(variant_array.is_null(0));
527        assert!(!variant_array.is_null(1));
528        assert_eq!(variant_array.value(1), Variant::from(42i32));
529        assert!(!variant_array.is_null(2));
530        let variant = variant_array.value(2);
531        let variant = variant.as_object().expect("variant to be an object");
532        assert_eq!(variant.get("foo").unwrap(), Variant::from("bar"));
533        assert!(!variant_array.is_null(3));
534        let variant = variant_array.value(3);
535        let list = variant.as_list().expect("variant to be a list");
536        assert_eq!(list.len(), 2);
537    }
538
539    #[test]
540    fn test_variant_array_builder_append_nulls() {
541        let mut builder = VariantArrayBuilder::new(6);
542        builder.append_variant(Variant::from(1i32));
543        builder.append_nulls(0); // should be a no-op
544        builder.append_nulls(3);
545        builder.append_variant(Variant::from(2i32));
546
547        let variant_array = builder.build();
548
549        assert_eq!(variant_array.len(), 5);
550        assert_eq!(variant_array.value(0), Variant::from(1i32));
551        assert!(variant_array.is_null(1));
552        assert!(variant_array.is_null(2));
553        assert!(variant_array.is_null(3));
554        assert_eq!(variant_array.value(4), Variant::from(2i32));
555    }
556
557    #[test]
558    fn test_extend_variant_array_builder() {
559        let mut b = VariantArrayBuilder::new(3);
560        b.extend([None, Some(Variant::Null), Some(Variant::from("norm"))]);
561
562        let variant_array = b.build();
563
564        assert_eq!(variant_array.len(), 3);
565        assert!(variant_array.is_null(0));
566        assert_eq!(variant_array.value(1), Variant::Null);
567        assert_eq!(
568            variant_array.value(2),
569            Variant::ShortString(ShortString::try_new("norm").unwrap())
570        );
571    }
572
573    #[test]
574    fn test_variant_value_array_builder_basic() {
575        let mut builder = VariantValueArrayBuilder::new(10);
576
577        // Append some values
578        builder.append_value(Variant::from(42i32));
579        builder.append_null();
580        builder.append_value(Variant::from("hello"));
581
582        let value_array = builder.build().unwrap();
583        assert_eq!(value_array.len(), 3);
584    }
585
586    #[test]
587    fn test_variant_value_array_builder_with_objects() {
588        // Populate a variant array with objects
589        let mut builder = VariantArrayBuilder::new(3);
590        builder
591            .new_object()
592            .with_field("name", "Alice")
593            .with_field("age", 30i32)
594            .finish();
595
596        builder
597            .new_object()
598            .with_field("name", "Bob")
599            .with_field("age", 42i32)
600            .with_field("city", "Wonderland")
601            .finish();
602
603        builder
604            .new_object()
605            .with_field("name", "Charlie")
606            .with_field("age", 1i32)
607            .finish();
608
609        let array = builder.build();
610
611        // Copy (some of) the objects over to the value array builder
612        //
613        // NOTE: Because we will reuse the metadata column, we cannot reorder rows. We can only
614        // filter or manipulate values within a row.
615        let mut value_builder = VariantValueArrayBuilder::new(3);
616
617        // straight copy
618        value_builder.append_value(array.value(0));
619
620        // filtering fields takes more work because we need to manually create an object builder
621        let value = array.value(1);
622        let mut builder = value_builder.builder_ext(value.metadata());
623        builder
624            .new_object()
625            .with_field("name", value.get_object_field("name").unwrap())
626            .with_field("age", value.get_object_field("age").unwrap())
627            .finish();
628
629        // same bytes, but now nested and duplicated inside a list
630        let value = array.value(2);
631        let mut builder = value_builder.builder_ext(value.metadata());
632        builder
633            .new_list()
634            .with_value(value.clone())
635            .with_value(value.clone())
636            .finish();
637
638        let array2 = VariantArray::from_parts(
639            array.metadata_column().clone(),
640            Arc::new(value_builder.build().unwrap()),
641            None,
642            None,
643        );
644
645        assert_eq!(array2.len(), 3);
646        assert_eq!(array.value(0), array2.value(0));
647
648        assert_eq!(
649            array.value(1).get_object_field("name"),
650            array2.value(1).get_object_field("name")
651        );
652        assert_eq!(
653            array.value(1).get_object_field("age"),
654            array2.value(1).get_object_field("age")
655        );
656
657        assert_eq!(array.value(2), array2.value(2).get_list_element(0).unwrap());
658        assert_eq!(array.value(2), array2.value(2).get_list_element(1).unwrap());
659    }
660}