using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
namespace IntelligentProjectAnalyzer.Serializers
{
#region Serializable Data Structures
///
/// The root object for the serialized prefab JSON.
/// Contains the flat list of all unique components and the root of the hierarchy.
///
[System.Serializable]
public class SerializablePrefab
{
public List components = new();
public SerializableGameObject hierarchy;
}
///
/// Represents a single, unique component type found in the prefab.
/// Its index in the main list serves as its ID.
///
[System.Serializable]
public class SerializableComponent
{
public string type;
}
///
/// Represents a single GameObject in the prefab's hierarchy.
/// This structure is recursive.
///
[System.Serializable]
public class SerializableGameObject
{
public string Name;
// A list of integers pointing to the indices of components in the SerializablePrefab's main component list.
public List ComponentIndices = new();
public List Children = new();
}
#endregion
///
/// A static class that provides functionality to serialize a Unity GameObject/Prefab
/// into a custom JSON format that describes its hierarchy and component structure.
///
public static class PrefabSerializer
{
///
/// A test menu item to allow easy serialization of a selected prefab.
///
[MenuItem("Assets/Serialize Selected Prefab to JSON")]
public static void SerializeSelectedPrefab()
{
var selectedObject = Selection.activeObject as GameObject;
var json = Serialize(selectedObject);
Debug.Log($"--- Serialized Prefab: {selectedObject?.name} ---\n{json}");
}
///
/// Serializes a GameObject into a JSON string with a specific format.
///
/// The root GameObject of the prefab to serialize.
/// A formatted JSON string representing the prefab's structure.
public static string Serialize(GameObject prefabRoot)
{
if (prefabRoot == null) return "{}";
// Ensure we are working with the root prefab asset, not an instance in the scene.
if (!PrefabUtility.IsPartOfPrefabAsset(prefabRoot))
{
Debug.LogError("The selected object is not a prefab asset. Please select a prefab from the Project window.");
return "{}";
}
// This dictionary will map a component type name to its unique index.
var componentTypeToIndexMap = new Dictionary();
var serializablePrefab = new SerializablePrefab();
// Start the recursive traversal from the root GameObject.
serializablePrefab.hierarchy = TraverseHierarchy(prefabRoot.transform, componentTypeToIndexMap, serializablePrefab.components);
return JsonUtility.ToJson(serializablePrefab, true);
}
public static bool TrySerialize(GameObject prefabRoot, out SerializablePrefab serializablePrefab)
{
serializablePrefab = new SerializablePrefab();
if (prefabRoot == null) return false;
// Ensure we are working with the root prefab asset, not an instance in the scene.
if (!PrefabUtility.IsPartOfPrefabAsset(prefabRoot))
{
Debug.LogError("The selected object is not a prefab asset. Please select a prefab from the Project window.");
return false;
}
// This dictionary will map a component type name to its unique index.
var componentTypeToIndexMap = new Dictionary();
// Start the recursive traversal from the root GameObject.
serializablePrefab.hierarchy = TraverseHierarchy(prefabRoot.transform, componentTypeToIndexMap, serializablePrefab.components);
return serializablePrefab != null;
}
///
/// Recursively traverses the transform hierarchy to build the serializable structure.
///
/// The transform of the GameObject currently being processed.
/// The lookup dictionary for component types to their indices.
/// The master list of unique component types.
/// A SerializableGameObject representing the current transform and its children.
private static SerializableGameObject TraverseHierarchy(Transform currentTransform, Dictionary componentMap, List componentList)
{
var serializableGo = new SerializableGameObject
{
Name = currentTransform.name
};
// Get all components on the current GameObject.
var componentsOnGo = currentTransform.GetComponents();
foreach (var component in componentsOnGo)
{
// Some components can be null if a script is missing.
if (component == null) continue;
var componentType = component.GetType().FullName;
if (componentType == null)
{
Debug.LogError($"Failed to serialize component on GameObject {currentTransform.name} because its type is null. Skipping this component.");
continue;
}
// If we haven't seen this component type before, add it to our master list.
if (!componentMap.ContainsKey(componentType))
{
componentMap[componentType] = componentList.Count;
componentList.Add(new SerializableComponent { type = componentType });
}
// Add the index of this component type to this GameObject's list.
serializableGo.ComponentIndices.Add(componentMap[componentType]);
}
// Recursively process all children.
foreach (Transform child in currentTransform)
{
serializableGo.Children.Add(TraverseHierarchy(child, componentMap, componentList));
}
return serializableGo;
}
}
}