private void AddValueLevel(
            object obj,
            Type parentType,
            string parentId)
        {
            if (obj == null)
            {
                return;
            }

            Type   type     = obj.GetType();
            string typeName = type.Name;

            var idPropertyName = _typeInfo.GetIdPropertyName(typeName);
            var typeData       = _typeInfo.PropertiesMap[type];

            string id = null;

            if (typeData.ValueProperties.Count > 0)
            {
                StringBuilder sb = new StringBuilder();
                bool          hasParentIdProperty = false;
                for (int i = 0; i < typeData.ValueProperties.Count; ++i)
                {
                    var    valueProperty = typeData.ValueProperties[i];
                    object value         = valueProperty.GetValue(obj);
                    if (valueProperty.Name == _typeInfo.IdPropertyName || valueProperty.Name == idPropertyName)
                    {
                        if (value == null)
                        {
                            _log.WriteWarning(nameof(AddValueLevel), obj, $"Id property {valueProperty.Name} of {typeName} is null");
                        }
                        id = value?.ToString() ?? string.Empty;
                    }
                    else
                    {
                        string strValue = string.Empty;
                        if (value != null)
                        {
                            if (valueProperty.PropertyType == typeof(DateTime))
                            {
                                strValue = DateTimeConverter.Convert((DateTime)value);
                            }
                            else if (valueProperty.PropertyType == typeof(DateTime?))
                            {
                                strValue = DateTimeConverter.Convert(((DateTime?)value).Value);
                            }
                            else if (StructureBuilder.GenericCollectionTypes.Any(t => t == valueProperty.PropertyType))
                            {
                                var strValues  = new List <string>();
                                var enumerable = (IEnumerable)value;
                                foreach (var item in enumerable)
                                {
                                    strValues.Add(item.ToString() ?? string.Empty);
                                }
                                strValue = string.Join(';', strValues);
                            }
                            else
                            {
                                strValue = value.ToString();
                            }
                        }
                        if (typeData.ParentIdPropertyName != null && valueProperty.Name == typeData.ParentIdPropertyName)
                        {
                            hasParentIdProperty = true;
                        }
                        if (sb.Length > 0 || i > 0 && (i != 1 || id == null))
                        {
                            sb.Append(',');
                        }
                        sb.Append(strValue);
                    }
                }

                if (parentId != null && !hasParentIdProperty)
                {
                    sb.Insert(0, $"{parentId},");
                }
                else if (sb.Length > 0 && parentType != null && _typeInfo.PropertiesMap[parentType].ValueProperties.Count > 0)
                {
                    _log.WriteWarning(
                        nameof(AddValueLevel),
                        obj,
                        $"Message of type {parentType.Name} doesn't have any identificators that can be used to make relations to its children");
                    sb.Insert(0, ",");
                }

                if (id != null)
                {
                    sb.Insert(0, $"{id},");
                }

                if (_objectsData.ContainsKey(typeName))
                {
                    _objectsData[typeName].Add(sb.ToString());
                }
                else
                {
                    _objectsData.Add(typeName, new List <string> {
                        sb.ToString()
                    });
                }
            }

            if (typeData.OneChildrenProperties.Count == 0 && typeData.ManyChildrenProperties.Count == 0)
            {
                return;
            }

            if (id == null)
            {
                id = GetIdFromChildren(obj, typeData);
            }
            if (id == null)
            {
                id = parentId;
            }
            if (id == null && typeData.RelationProperty != null)
            {
                id = typeData.RelationProperty.GetValue(obj)?.ToString();
            }

            foreach (var childProperty in typeData.OneChildrenProperties)
            {
                object value = childProperty.GetValue(obj);
                if (value == null)
                {
                    continue;
                }

                ProcessTypeItem(
                    value,
                    type,
                    childProperty == typeData.ChildWithIdProperty ? null : id);
            }

            foreach (var childrenProperty in typeData.ManyChildrenProperties)
            {
                object value = childrenProperty.GetValue(obj);
                if (value == null)
                {
                    continue;
                }

                var items = value as IEnumerable;
                if (items == null)
                {
                    throw new InvalidOperationException($"Couldn't cast value '{value}' of property {childrenProperty.Name} from {typeName} to IEnumerable");
                }
                foreach (var item in items)
                {
                    ProcessTypeItem(
                        item,
                        type,
                        childrenProperty == typeData.ChildWithIdProperty ? null : id);
                }
            }
        }