static void AddItem(Dictionary <int, PackedData.Property> children, int fieldID,
                                PackedData.Type type, PackedData.Descriptor childDescriptor, object value)
            {
                if (children.ContainsKey(fieldID) == false)
                {
                    children[fieldID] = new PackedData.Property()
                    {
                        Descriptor = childDescriptor,
                        Type       = type,
                        Value      = value
                    };
                }
                else if (children[fieldID].Type == type)
                {
                    var array = Array.CreateInstance(ArrayType(type), 2);
                    array.SetValue(children[fieldID].Value, 0);
                    array.SetValue(value, 1);
                    children[fieldID] = new PackedData.Property()
                    {
                        Descriptor = childDescriptor,
                        Type       = ConvertToArray(type),
                        Value      = array
                    };
                }
                else if (children[fieldID].Type == ConvertToArray(type))
                {
                    // TODO: This is expensive. We should preprocess the data stream
                    // beforehand to know how many array elements must be allocated in advance.

                    var previous = (Array)children[fieldID].Value;
                    var next     = Array.CreateInstance(ArrayType(type), previous.Length + 1);
                    previous.CopyTo(next, 0);
                    next.SetValue(value, next.Length - 1);
                    children[fieldID] = new PackedData.Property()
                    {
                        Descriptor = childDescriptor,
                        Type       = ConvertToArray(type),
                        Value      = next
                    };
                }
                else
                {
                    throw new Exception($"Inconsistent data types ({type} in a {children[fieldID].Type}) for {childDescriptor}");
                }
            }
        /// <summary>
        /// Unpack a packed binary stream into a set of description objects contained
        /// inside the 'PackedData' class. This should be called only with valid streams
        /// (top level are always valid; if the stream is inside a property,
        /// IsValidContainer should be called to ensure no errors).
        /// </summary>
        /// <returns>An array of Property[] objects</returns>
        public static PackedData.Property[] Unpack(ReadOnlySpan <byte> data, PackedData.Descriptor descriptor)
        {
            var children = new Dictionary <int, PackedData.Property>();

            int index = 0;

            while (index < data.Length)
            {
                int chunk           = ReadInt(data, ref index);
                int fieldID         = (chunk >> 3);
                int encoding        = (chunk & 0x7);
                var childDescriptor = new PackedData.Descriptor {
                    id = fieldID, parent = descriptor
                };
                switch (encoding)
                {
                case 0:         // Integer
                {
                    int value = ReadInt(data, ref index);
                    AddItem(children, fieldID, PackedData.Type.Integer, childDescriptor, value);
                    break;
                }

                case 2:         // Struct or string
                {
                    var length = ReadInt(data, ref index);
                    var slice  = data.Slice(index, length);
                    index += length;

                    // Force string interpretation if we already have objets
                    bool isString = IsString(slice) || !IsValidContainer(slice) || length == 0;
                    if (children.ContainsKey(fieldID) && children[fieldID].Type == PackedData.Type.StringArray)
                    {
                        isString = true;
                    }

                    if (isString)                // String
                    {
                        // Detect internal string encoding. Actually, this could be a case of
                        // trying to deserialize a string[] instead of a string. Further tests needed!
                        if (slice.Length > 1 && slice[0] == 10)
                        {
                            int subindex = 1;
                            while (ReadInt(slice, ref subindex) == slice.Length - subindex && subindex <= slice.Length)
                            {
                                slice = slice.Slice(subindex);
                                if (slice.Length < 2 || slice[0] != 10)
                                {
                                    break;
                                }
                                subindex = 1;
                            }
                        }
                        var value = Encoding.UTF8.GetString(slice);
                        AddItem(children, fieldID, PackedData.Type.String, childDescriptor, value);
                    }
                    else
                    {
                        var value = Unpack(slice, childDescriptor);
                        AddItem(children, fieldID, PackedData.Type.Object, childDescriptor, value);
                    }
                    break;
                }

                case 5:         // Float
                {
                    var value = BitConverter.ToSingle(data.Slice(index, 4));
                    index += 4;

                    AddItem(children, fieldID, PackedData.Type.Float, childDescriptor, value);
                    break;
                }
                }
            }
            return(children.Values.ToArray());