Загрузка данных


#if UNITY_EDITOR

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;

namespace CodeBase.EditorTools
{
    public sealed class SwaggerDtoToCSharpConverterWindow : EditorWindow
    {
        private const string DefaultRootClassName = "RootDto";

        private string _inputJson = string.Empty;
        private string _outputCode = string.Empty;

        private Vector2 _inputScrollPosition;
        private Vector2 _outputScrollPosition;

        [MenuItem("Tools/Swagger DTO To C#")]
        public static void Open()
        {
            SwaggerDtoToCSharpConverterWindow window = GetWindow<SwaggerDtoToCSharpConverterWindow>("Swagger DTO To C#");
            window.minSize = new Vector2(800f, 600f);
            window.Show();
        }

        private void OnGUI()
        {
            EditorGUILayout.LabelField("Swagger DTO JSON", EditorStyles.boldLabel);

            _inputScrollPosition = EditorGUILayout.BeginScrollView(
                _inputScrollPosition,
                GUILayout.MinHeight(220f),
                GUILayout.ExpandHeight(true));

            _inputJson = EditorGUILayout.TextArea(_inputJson, GUILayout.ExpandHeight(true));

            EditorGUILayout.EndScrollView();

            EditorGUILayout.Space(8f);

            if (GUILayout.Button("Convert", GUILayout.Height(32f)))
            {
                Convert();
            }

            EditorGUILayout.Space(8f);

            EditorGUILayout.LabelField("C# Class", EditorStyles.boldLabel);

            _outputScrollPosition = EditorGUILayout.BeginScrollView(
                _outputScrollPosition,
                GUILayout.MinHeight(260f),
                GUILayout.ExpandHeight(true));

            _outputCode = EditorGUILayout.TextArea(_outputCode, GUILayout.ExpandHeight(true));

            EditorGUILayout.EndScrollView();
        }

        private void Convert()
        {
            if (string.IsNullOrWhiteSpace(_inputJson))
            {
                _outputCode = "// Input is empty.";
                return;
            }

            try
            {
                SwaggerDtoCSharpClassGenerator generator = new SwaggerDtoCSharpClassGenerator();
                _outputCode = generator.Generate(_inputJson, DefaultRootClassName);
            }
            catch (Exception exception)
            {
                _outputCode = $"// Convert failed:\n// {exception.Message}";
            }
        }
    }

    internal sealed class SwaggerDtoCSharpClassGenerator
    {
        private readonly List<ClassDefinition> _classDefinitions = new List<ClassDefinition>();
        private readonly Dictionary<string, JObject> _schemasByName = new Dictionary<string, JObject>();
        private readonly Dictionary<string, string> _generatedClassNamesBySchemaName = new Dictionary<string, string>();
        private readonly HashSet<string> _usedClassNames = new HashSet<string>();

        public string Generate(string json, string defaultRootClassName)
        {
            _classDefinitions.Clear();
            _schemasByName.Clear();
            _generatedClassNamesBySchemaName.Clear();
            _usedClassNames.Clear();

            JObject rootObject = ParseInput(json);
            JObject schemasObject = ExtractSchemasObject(rootObject);

            if (schemasObject != null)
            {
                RegisterSchemas(schemasObject);

                foreach (JProperty schemaProperty in schemasObject.Properties())
                {
                    if (schemaProperty.Value is JObject schemaObject)
                    {
                        BuildClassFromSchema(schemaProperty.Name, schemaObject);
                    }
                }
            }
            else if (LooksLikeSingleSchema(rootObject))
            {
                BuildClassFromSchema(defaultRootClassName, rootObject);
            }
            else
            {
                RegisterSchemas(rootObject);

                foreach (JProperty schemaProperty in rootObject.Properties())
                {
                    if (schemaProperty.Value is JObject schemaObject && LooksLikeSingleSchema(schemaObject))
                    {
                        BuildClassFromSchema(schemaProperty.Name, schemaObject);
                    }
                }
            }

            return BuildOutputCode();
        }

        private JObject ParseInput(string json)
        {
            string trimmedJson = json.Trim();

            try
            {
                return JObject.Parse(trimmedJson);
            }
            catch
            {
                if (trimmedJson.StartsWith("\"", StringComparison.Ordinal))
                {
                    return JObject.Parse("{" + trimmedJson.TrimEnd(',') + "}");
                }

                throw;
            }
        }

        private JObject ExtractSchemasObject(JObject rootObject)
        {
            JToken componentsSchemasToken = rootObject["components"]?["schemas"];

            if (componentsSchemasToken is JObject componentsSchemasObject)
            {
                return componentsSchemasObject;
            }

            JToken definitionsToken = rootObject["definitions"];

            if (definitionsToken is JObject definitionsObject)
            {
                return definitionsObject;
            }

            return null;
        }

        private void RegisterSchemas(JObject schemasObject)
        {
            foreach (JProperty schemaProperty in schemasObject.Properties())
            {
                if (schemaProperty.Value is JObject schemaObject)
                {
                    _schemasByName[schemaProperty.Name] = schemaObject;
                }
            }
        }

        private bool LooksLikeSingleSchema(JObject objectValue)
        {
            return objectValue["properties"] is JObject ||
                   objectValue["type"] != null ||
                   objectValue["allOf"] is JArray ||
                   objectValue["$ref"] != null;
        }

        private string BuildClassFromSchema(string suggestedClassName, JObject schemaObject)
        {
            string normalizedSuggestedClassName = NormalizeClassName(suggestedClassName);

            if (_generatedClassNamesBySchemaName.TryGetValue(suggestedClassName, out string existingClassName))
            {
                return existingClassName;
            }

            string className = GetUniqueClassName(normalizedSuggestedClassName);
            _generatedClassNamesBySchemaName[suggestedClassName] = className;

            ClassDefinition classDefinition = new ClassDefinition(className);
            _classDefinitions.Add(classDefinition);

            JObject propertiesObject = GetMergedProperties(schemaObject);

            if (propertiesObject == null)
            {
                return className;
            }

            HashSet<string> usedFieldNames = new HashSet<string>();

            foreach (JProperty property in propertiesObject.Properties())
            {
                if (property.Value is not JObject propertySchemaObject)
                {
                    continue;
                }

                string fieldName = GetUniqueFieldName(ToPascalCase(property.Name), usedFieldNames);
                string fieldTypeName = ResolveType(property.Name, propertySchemaObject);

                classDefinition.Fields.Add(new FieldDefinition(fieldTypeName, fieldName));
            }

            return className;
        }

        private JObject GetMergedProperties(JObject schemaObject)
        {
            JObject result = new JObject();

            if (schemaObject["allOf"] is JArray allOfArray)
            {
                foreach (JToken itemToken in allOfArray)
                {
                    if (itemToken is not JObject itemObject)
                    {
                        continue;
                    }

                    JObject resolvedObject = ResolveSchemaObject(itemObject);
                    JObject itemPropertiesObject = GetMergedProperties(resolvedObject);

                    if (itemPropertiesObject == null)
                    {
                        continue;
                    }

                    foreach (JProperty property in itemPropertiesObject.Properties())
                    {
                        result[property.Name] = property.Value;
                    }
                }
            }

            if (schemaObject["properties"] is JObject propertiesObject)
            {
                foreach (JProperty property in propertiesObject.Properties())
                {
                    result[property.Name] = property.Value;
                }
            }

            return result.Properties().Any() ? result : null;
        }

        private JObject ResolveSchemaObject(JObject schemaObject)
        {
            string referencePath = schemaObject.Value<string>("$ref");

            if (string.IsNullOrWhiteSpace(referencePath))
            {
                return schemaObject;
            }

            string schemaName = GetSchemaNameFromReference(referencePath);

            if (_schemasByName.TryGetValue(schemaName, out JObject referencedSchemaObject))
            {
                return referencedSchemaObject;
            }

            return schemaObject;
        }

        private string ResolveType(string propertyName, JObject propertySchemaObject)
        {
            if (propertySchemaObject["$ref"] != null)
            {
                string referencePath = propertySchemaObject.Value<string>("$ref");
                string schemaName = GetSchemaNameFromReference(referencePath);

                if (_schemasByName.TryGetValue(schemaName, out JObject referencedSchemaObject))
                {
                    return BuildClassFromSchema(schemaName, referencedSchemaObject);
                }

                return NormalizeClassName(schemaName);
            }

            if (propertySchemaObject["allOf"] is JArray allOfArray)
            {
                foreach (JToken itemToken in allOfArray)
                {
                    if (itemToken is JObject itemObject)
                    {
                        return ResolveType(propertyName, itemObject);
                    }
                }
            }

            string typeName = propertySchemaObject.Value<string>("type");
            string formatName = propertySchemaObject.Value<string>("format");

            string resolvedTypeName;

            switch (typeName)
            {
                case "integer":
                    resolvedTypeName = ResolveIntegerType(formatName);
                    break;

                case "number":
                    resolvedTypeName = ResolveNumberType(formatName);
                    break;

                case "boolean":
                    resolvedTypeName = "bool";
                    break;

                case "string":
                    resolvedTypeName = ResolveStringType(formatName);
                    break;

                case "array":
                    resolvedTypeName = ResolveArrayType(propertyName, propertySchemaObject);
                    break;

                case "object":
                    resolvedTypeName = ResolveObjectType(propertyName, propertySchemaObject);
                    break;

                default:
                    if (propertySchemaObject["enum"] is JArray)
                    {
                        resolvedTypeName = "string";
                    }
                    else if (propertySchemaObject["properties"] is JObject)
                    {
                        resolvedTypeName = BuildClassFromSchema(propertyName, propertySchemaObject);
                    }
                    else
                    {
                        resolvedTypeName = "object";
                    }

                    break;
            }

            bool isNullable = propertySchemaObject.Value<bool?>("nullable") == true;

            if (isNullable && IsNullableValueType(resolvedTypeName))
            {
                return resolvedTypeName + "?";
            }

            return resolvedTypeName;
        }

        private string ResolveIntegerType(string formatName)
        {
            switch (formatName)
            {
                case "int64":
                    return "long";

                case "int32":
                default:
                    return "int";
            }
        }

        private string ResolveNumberType(string formatName)
        {
            switch (formatName)
            {
                case "float":
                    return "float";

                case "decimal":
                    return "decimal";

                case "double":
                default:
                    return "double";
            }
        }

        private string ResolveStringType(string formatName)
        {
            switch (formatName)
            {
                case "date":
                case "date-time":
                    return "DateTime";

                case "uuid":
                    return "Guid";

                case "byte":
                case "binary":
                    return "byte[]";

                default:
                    return "string";
            }
        }

        private string ResolveArrayType(string propertyName, JObject propertySchemaObject)
        {
            if (propertySchemaObject["items"] is not JObject itemsObject)
            {
                return "List<object>";
            }

            string itemSuggestedName = ToSingularPascalCase(propertyName);
            string itemTypeName = ResolveType(itemSuggestedName, itemsObject);

            return $"List<{itemTypeName}>";
        }

        private string ResolveObjectType(string propertyName, JObject propertySchemaObject)
        {
            if (propertySchemaObject["properties"] is JObject)
            {
                return BuildClassFromSchema(propertyName, propertySchemaObject);
            }

            return "Dictionary<string, object>";
        }

        private string GetSchemaNameFromReference(string referencePath)
        {
            int lastSlashIndex = referencePath.LastIndexOf("/", StringComparison.Ordinal);

            if (lastSlashIndex < 0)
            {
                return referencePath;
            }

            return referencePath.Substring(lastSlashIndex + 1);
        }

        private string BuildOutputCode()
        {
            StringBuilder builder = new StringBuilder();

            builder.AppendLine("using System;");
            builder.AppendLine("using System.Collections.Generic;");
            builder.AppendLine();

            foreach (ClassDefinition classDefinition in _classDefinitions)
            {
                builder.AppendLine("[Serializable]");
                builder.AppendLine($"public class {classDefinition.Name}");
                builder.AppendLine("{");

                foreach (FieldDefinition field in classDefinition.Fields)
                {
                    builder.AppendLine($"    public {field.TypeName} {field.Name};");
                }

                builder.AppendLine("}");
                builder.AppendLine();
            }

            return builder.ToString();
        }

        private string GetUniqueClassName(string suggestedClassName)
        {
            string className = suggestedClassName;
            int index = 2;

            while (_usedClassNames.Contains(className))
            {
                className = $"{suggestedClassName}{index}";
                index++;
            }

            _usedClassNames.Add(className);

            return className;
        }

        private string GetUniqueFieldName(string suggestedFieldName, HashSet<string> usedFieldNames)
        {
            string fieldName = NormalizeFieldName(suggestedFieldName);
            string baseFieldName = fieldName;

            int index = 2;

            while (usedFieldNames.Contains(fieldName))
            {
                fieldName = $"{baseFieldName}{index}";
                index++;
            }

            usedFieldNames.Add(fieldName);

            return fieldName;
        }

        private string NormalizeClassName(string value)
        {
            string className = ToPascalCase(value);

            if (IsCSharpKeyword(className))
            {
                return className + "Class";
            }

            return className;
        }

        private string NormalizeFieldName(string value)
        {
            string fieldName = ToPascalCase(value);

            if (IsCSharpKeyword(fieldName))
            {
                return fieldName + "Value";
            }

            return fieldName;
        }

        private string ToSingularPascalCase(string value)
        {
            string pascalCase = ToPascalCase(value);

            if (pascalCase.EndsWith("ies", StringComparison.OrdinalIgnoreCase) && pascalCase.Length > 3)
            {
                return pascalCase.Substring(0, pascalCase.Length - 3) + "y";
            }

            if (pascalCase.EndsWith("s", StringComparison.OrdinalIgnoreCase) &&
                !pascalCase.EndsWith("ss", StringComparison.OrdinalIgnoreCase) &&
                pascalCase.Length > 1)
            {
                return pascalCase.Substring(0, pascalCase.Length - 1);
            }

            return pascalCase + "Item";
        }

        private string ToPascalCase(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return "Value";
            }

            StringBuilder builder = new StringBuilder();
            bool makeUpper = true;

            foreach (char character in value)
            {
                if (!char.IsLetterOrDigit(character))
                {
                    makeUpper = true;
                    continue;
                }

                if (builder.Length == 0 && char.IsDigit(character))
                {
                    builder.Append("Value");
                }

                builder.Append(makeUpper
                    ? char.ToUpperInvariant(character)
                    : character);

                makeUpper = false;
            }

            return builder.Length == 0 ? "Value" : builder.ToString();
        }

        private bool IsNullableValueType(string typeName)
        {
            switch (typeName)
            {
                case "int":
                case "long":
                case "float":
                case "double":
                case "decimal":
                case "bool":
                case "DateTime":
                case "Guid":
                    return true;

                default:
                    return false;
            }
        }

        private bool IsCSharpKeyword(string value)
        {
            switch (value)
            {
                case "abstract":
                case "as":
                case "base":
                case "bool":
                case "break":
                case "byte":
                case "case":
                case "catch":
                case "char":
                case "checked":
                case "class":
                case "const":
                case "continue":
                case "decimal":
                case "default":
                case "delegate":
                case "do":
                case "double":
                case "else":
                case "enum":
                case "event":
                case "explicit":
                case "extern":
                case "false":
                case "finally":
                case "fixed":
                case "float":
                case "for":
                case "foreach":
                case "goto":
                case "if":
                case "implicit":
                case "in":
                case "int":
                case "interface":
                case "internal":
                case "is":
                case "lock":
                case "long":
                case "namespace":
                case "new":
                case "null":
                case "object":
                case "operator":
                case "out":
                case "override":
                case "params":
                case "private":
                case "protected":
                case "public":
                case "readonly":
                case "ref":
                case "return":
                case "sbyte":
                case "sealed":
                case "short":
                case "sizeof":
                case "stackalloc":
                case "static":
                case "string":
                case "struct":
                case "switch":
                case "this":
                case "throw":
                case "true":
                case "try":
                case "typeof":
                case "uint":
                case "ulong":
                case "unchecked":
                case "unsafe":
                case "ushort":
                case "using":
                case "virtual":
                case "void":
                case "volatile":
                case "while":
                    return true;

                default:
                    return false;
            }
        }

        private sealed class ClassDefinition
        {
            public readonly string Name;
            public readonly List<FieldDefinition> Fields = new List<FieldDefinition>();

            public ClassDefinition(string name)
            {
                Name = name;
            }
        }

        private sealed class FieldDefinition
        {
            public readonly string TypeName;
            public readonly string Name;

            public FieldDefinition(string typeName, string name)
            {
                TypeName = typeName;
                Name = name;
            }
        }
    }
}

#endif