using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using UnityEditor; using UnityEngine; namespace BrewMonster.Utils { public class JsonScriptableObjectImporterWindow : EditorWindow { private const string DefaultFieldTemplate = @"public string m_strName; public int m_id; public string m_strPath; public int m_iRowNum; public int m_iColNum; public int m_bLimitJump; public string[] m_routeFiles; public string m_content;"; private TextAsset jsonFile; private DefaultAsset outputFolder; private string dataTypeName = "JsonData"; private string assetTypeName = "JsonDataAsset"; private string assetFileName = "JsonDataAsset"; private string fieldDefinitions = DefaultFieldTemplate; private bool generateStruct; private Vector2 scrollPosition; [MenuItem("Tools/JSON ScriptableObject Importer")] public static void OpenWindow() { GetWindow("JSON -> ScriptableObject"); } private void OnGUI() { scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); EditorGUILayout.Space(); EditorGUILayout.HelpBox( "1. Keo file JSON vao o ben duoi.\n" + "2. Dien ten type va field cho struct/class.\n" + "3. Bam Generate Runtime Types de tao file C# trong project Unity.\n" + "4. Cho Unity compile xong, bam Create Asset From JSON.", MessageType.Info); DrawJsonDropZone(); EditorGUILayout.Space(); jsonFile = (TextAsset)EditorGUILayout.ObjectField("JSON Input", jsonFile, typeof(TextAsset), false); outputFolder = (DefaultAsset)EditorGUILayout.ObjectField("Output Folder", outputFolder, typeof(DefaultAsset), false); EditorGUILayout.Space(); EditorGUILayout.LabelField("Type Settings", EditorStyles.boldLabel); dataTypeName = EditorGUILayout.TextField("Data Type Name", dataTypeName); assetTypeName = EditorGUILayout.TextField("Asset Type Name", assetTypeName); assetFileName = EditorGUILayout.TextField("Asset File Name", assetFileName); generateStruct = EditorGUILayout.Toggle("Generate As Struct", generateStruct); EditorGUILayout.Space(); EditorGUILayout.LabelField("Struct/Class Fields", EditorStyles.boldLabel); EditorGUILayout.HelpBox( "Dien cac field theo cu phap C#. Vi du: public string name; public int id; public string[] tags;", MessageType.None); fieldDefinitions = EditorGUILayout.TextArea(fieldDefinitions, GUILayout.MinHeight(220)); EditorGUILayout.Space(); using (new EditorGUILayout.HorizontalScope()) { if (GUILayout.Button("Generate Runtime Types", GUILayout.Height(32))) { GenerateRuntimeTypes(); } if (GUILayout.Button("Create Asset From JSON", GUILayout.Height(32))) { CreateAssetFromJson(); } } EditorGUILayout.EndScrollView(); } private void DrawJsonDropZone() { Rect dropArea = GUILayoutUtility.GetRect(0f, 70f, GUILayout.ExpandWidth(true)); GUI.Box(dropArea, "Drop file JSON vao day", EditorStyles.helpBox); Event currentEvent = Event.current; if (!dropArea.Contains(currentEvent.mousePosition)) { return; } if (currentEvent.type == EventType.DragUpdated || currentEvent.type == EventType.DragPerform) { DragAndDrop.visualMode = DragAndDropVisualMode.Copy; if (currentEvent.type == EventType.DragPerform) { DragAndDrop.AcceptDrag(); foreach (UnityEngine.Object draggedObject in DragAndDrop.objectReferences) { if (draggedObject is TextAsset textAsset) { jsonFile = textAsset; GUI.FocusControl(null); break; } } } currentEvent.Use(); } } private void GenerateRuntimeTypes() { if (!ValidateGenerateInputs()) { return; } string folderPath = AssetDatabase.GetAssetPath(outputFolder); string sanitizedDataTypeName = SanitizeIdentifier(dataTypeName, "JsonData"); string sanitizedAssetTypeName = SanitizeIdentifier(assetTypeName, sanitizedDataTypeName + "Asset"); string outputPath = Path.Combine(folderPath, sanitizedAssetTypeName + ".cs").Replace("\\", "/"); string source = BuildRuntimeSource( sanitizedDataTypeName, sanitizedAssetTypeName, generateStruct, fieldDefinitions); File.WriteAllText(outputPath, source, new UTF8Encoding(false)); AssetDatabase.Refresh(); Debug.Log("Generated runtime types at: " + outputPath); EditorUtility.DisplayDialog( "Generate Complete", "Da tao file C# runtime. Hay doi Unity compile xong roi bam 'Create Asset From JSON'.", "OK"); } private void CreateAssetFromJson() { if (!ValidateImportInputs()) { return; } string sanitizedDataTypeName = SanitizeIdentifier(dataTypeName, "JsonData"); string sanitizedAssetTypeName = SanitizeIdentifier(assetTypeName, sanitizedDataTypeName + "Asset"); string jsonText = jsonFile.text; Type dataType = FindType(sanitizedDataTypeName); Type assetType = FindType(sanitizedAssetTypeName); Type wrapperType = FindType(sanitizedDataTypeName + "ArrayWrapper"); if (dataType == null || assetType == null || wrapperType == null) { EditorUtility.DisplayDialog( "Types Not Ready", "Khong tim thay cac type vua generate. Hay chac chan Unity da compile xong sau khi bam Generate.", "OK"); return; } ScriptableObject asset = ScriptableObject.CreateInstance(assetType); SetFieldIfExists(assetType, asset, "rawJson", jsonText); string trimmedJson = jsonText.TrimStart(); if (trimmedJson.StartsWith("[", StringComparison.Ordinal)) { object wrapper = JsonUtility.FromJson("{\"items\":" + jsonText + "}", wrapperType); object items = wrapperType.GetField("items", BindingFlags.Public | BindingFlags.Instance)?.GetValue(wrapper); SetFieldIfExists(assetType, asset, "items", items); } else { object data = JsonUtility.FromJson(jsonText, dataType); SetFieldIfExists(assetType, asset, "data", data); } string folderPath = AssetDatabase.GetAssetPath(outputFolder); string requestedName = string.IsNullOrWhiteSpace(assetFileName) ? sanitizedAssetTypeName : assetFileName.Trim(); string assetPath = AssetDatabase.GenerateUniqueAssetPath( Path.Combine(folderPath, requestedName + ".asset").Replace("\\", "/")); AssetDatabase.CreateAsset(asset, assetPath); EditorUtility.SetDirty(asset); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Selection.activeObject = asset; Debug.Log("Created ScriptableObject asset at: " + assetPath); EditorUtility.DisplayDialog("Import Complete", "Da tao asset tu JSON thanh cong.", "OK"); } private bool ValidateGenerateInputs() { if (outputFolder == null) { EditorUtility.DisplayDialog("Missing Output Folder", "Hay chon folder de tao file C# runtime.", "OK"); return false; } string folderPath = AssetDatabase.GetAssetPath(outputFolder); if (!AssetDatabase.IsValidFolder(folderPath)) { EditorUtility.DisplayDialog("Invalid Folder", "Output Folder phai la mot folder trong Assets.", "OK"); return false; } if (string.IsNullOrWhiteSpace(dataTypeName) || string.IsNullOrWhiteSpace(assetTypeName)) { EditorUtility.DisplayDialog("Missing Type Names", "Hay nhap Data Type Name va Asset Type Name.", "OK"); return false; } if (string.IsNullOrWhiteSpace(fieldDefinitions)) { EditorUtility.DisplayDialog("Missing Fields", "Hay nhap field cho struct/class.", "OK"); return false; } return true; } private bool ValidateImportInputs() { if (!ValidateGenerateInputs()) { return false; } if (jsonFile == null) { EditorUtility.DisplayDialog("Missing JSON", "Hay keo hoac chon file JSON dau vao.", "OK"); return false; } return true; } private static string BuildRuntimeSource( string dataType, string assetType, bool asStruct, string fields) { string keyword = asStruct ? "struct" : "class"; string createMenuName = "Tools/Generated/" + assetType; StringBuilder builder = new StringBuilder(); builder.AppendLine("using System;"); builder.AppendLine("using UnityEngine;"); builder.AppendLine(); builder.AppendLine("[Serializable]"); builder.AppendLine("public " + keyword + " " + dataType); builder.AppendLine("{"); foreach (string line in NormalizeFieldLines(fields)) { builder.AppendLine(" " + line); } builder.AppendLine("}"); builder.AppendLine(); builder.AppendLine("[CreateAssetMenu(fileName = \"" + assetType + "\", menuName = \"" + createMenuName + "\")]"); builder.AppendLine("public class " + assetType + " : ScriptableObject"); builder.AppendLine("{"); builder.AppendLine(" public " + dataType + " data;"); builder.AppendLine(" public " + dataType + "[] items;"); builder.AppendLine(" [TextArea(5, 20)]"); builder.AppendLine(" public string rawJson;"); builder.AppendLine("}"); builder.AppendLine(); builder.AppendLine("[Serializable]"); builder.AppendLine("public class " + dataType + "ArrayWrapper"); builder.AppendLine("{"); builder.AppendLine(" public " + dataType + "[] items;"); builder.AppendLine("}"); return builder.ToString(); } private static IEnumerable NormalizeFieldLines(string fields) { return fields .Replace("\r\n", "\n") .Split('\n') .Select(line => line.Trim()) .Where(line => !string.IsNullOrWhiteSpace(line)); } private static void SetFieldIfExists(Type targetType, object target, string fieldName, object value) { FieldInfo field = targetType.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance); if (field != null) { field.SetValue(target, value); } } private static string SanitizeIdentifier(string value, string fallback) { if (string.IsNullOrWhiteSpace(value)) { return fallback; } List chars = new List(); foreach (char character in value.Trim()) { if (char.IsLetterOrDigit(character) || character == '_') { chars.Add(character); } } if (chars.Count == 0) { return fallback; } if (!char.IsLetter(chars[0]) && chars[0] != '_') { chars.Insert(0, '_'); } return new string(chars.ToArray()); } private static Type FindType(string typeName) { foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { Type directMatch = assembly.GetType(typeName); if (directMatch != null) { return directMatch; } Type fallbackMatch = GetTypesSafely(assembly).FirstOrDefault(type => type.Name == typeName); if (fallbackMatch != null) { return fallbackMatch; } } return null; } private static IEnumerable GetTypesSafely(Assembly assembly) { try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException exception) { return exception.Types.Where(type => type != null); } } } }