| 
					
				 | 
			
			
				@@ -2,108 +2,156 @@ using System.Collections.Generic; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 using System.Linq; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 using AssetBank.Settings; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 using Newtonsoft.Json.Linq; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+using UnityEngine; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 namespace AssetBank.Editor.Tools 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     public static class JsonDataTrimmer 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        // The list of default MonoBehaviour properties to be used for comparison. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        private static readonly HashSet<string> DefaultMonoBehaviourProperties = new HashSet<string> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "m_ObjectHideFlags", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "m_CorrespondingSourceObject", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "m_PrefabInstance", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "m_PrefabAsset", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "m_GameObject", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "m_Enabled", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "m_EditorHideFlags", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "m_Script", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "m_Name", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "m_EditorClassIdentifier" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         public static string Process(string rawJsonText, ProjectExporterSettings settings) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             var allComponents = JArray.Parse(rawJsonText); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            var rules = settings.SafeComponents.ToDictionary(r => r.Component, r => r.ExposedFields); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // Pass 1: Index all components by their anchor_id 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            var componentMap = allComponents.Children<JObject>() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                .Where(c => c["anchor_id"] != null) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                .ToDictionary(c => c["anchor_id"].ToString(), c => c); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+             
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            // Pass 1: Build the definitive ruleset 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var rules = settings.SafeComponents.ToDictionary(r => r.Component, r => r.ExposedFields.ToHashSet()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (!settings.OverrideHardcodedDefaults) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                foreach (var hardcodedRule in settings.HardcodedComponents) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (rules.TryGetValue(hardcodedRule.Component, out var userRules)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        userRules.UnionWith(hardcodedRule.ExposedFields); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    else 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        rules[hardcodedRule.Component] = hardcodedRule.ExposedFields.ToHashSet(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // Pass 2: Process and trim each component in the map 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            foreach (var component in componentMap.Values) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            // Pass 2: Create a fully trimmed copy of every component 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var trimmedComponentMap = new Dictionary<string, JObject>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            foreach (var component in allComponents.Children<JObject>()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                var dataBlock = (JObject)component["data"]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                if (dataBlock == null || !dataBlock.HasValues) continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var anchorId = component["anchor_id"]?.ToString(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (string.IsNullOrEmpty(anchorId)) continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var trimmedComponent = component.DeepClone() as JObject; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var dataBlock = trimmedComponent["data"] as JObject; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (dataBlock == null || !dataBlock.HasValues) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    trimmedComponentMap[anchorId] = trimmedComponent; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 var componentProperty = dataBlock.Properties().FirstOrDefault(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                if (componentProperty == null) continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (componentProperty == null) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    trimmedComponentMap[anchorId] = trimmedComponent; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 var componentName = componentProperty.Name; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                var componentData = (JObject)componentProperty.Value; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var componentJObject = componentProperty.Value as JObject; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (componentJObject == null) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    trimmedComponentMap[anchorId] = trimmedComponent; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 if (rules.TryGetValue(componentName, out var exposedFields)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    if (exposedFields.Contains("*")) continue; // Keep everything 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    var newComponentData = new JObject(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    if (componentName == "MonoBehaviour") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (exposedFields.Contains("*")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        foreach (var field in exposedFields) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                            if (componentData.TryGetValue(field, out var value)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                                newComponentData.Add(field, value); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        foreach (var property in componentData.Properties()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                            if (!DefaultMonoBehaviourProperties.Contains(property.Name) && newComponentData[property.Name] == null) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                                newComponentData.Add(property.Name, property.Value); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        trimmedComponentMap[anchorId] = trimmedComponent; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    else 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var propertiesToKeep = new HashSet<string>(exposedFields); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var currentKeys = componentJObject.Properties().Select(p => p.Name).ToList(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    foreach (var key in currentKeys) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        foreach (var field in exposedFields) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        if (!propertiesToKeep.Contains(key)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                         { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                            if (componentData.TryGetValue(field, out var value)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                                newComponentData.Add(field, value); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            componentJObject.Remove(key); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    dataBlock[componentName] = newComponentData; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 else 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    dataBlock[componentName] = new JObject(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    componentJObject.RemoveAll(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                trimmedComponentMap[anchorId] = trimmedComponent; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            // Pass 3: Build the final list of GameObjects with embedded components 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+             
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            // Pass 3: Final Assembly 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             var finalOutput = new JArray(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            foreach (var component in componentMap.Values) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            var embeddedIds = new HashSet<string>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            // First, identify all components that will be embedded into GameObjects 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            foreach (var originalComponent in allComponents.Children<JObject>()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (originalComponent["data"]?["GameObject"]?["m_Component"] is JArray componentList) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    foreach (var compRef in componentList.Children<JObject>()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        if (compRef["component"]?["fileID"]?.ToString() is var fileID && !string.IsNullOrEmpty(fileID)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            embeddedIds.Add(fileID); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            // Now, build the final output, preserving order 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            foreach (var originalComponent in allComponents.Children<JObject>()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                var dataBlock = (JObject)component["data"]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                if (dataBlock?["GameObject"] == null) continue; // We only want GameObjects in the final list 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var anchorId = originalComponent["anchor_id"]?.ToString(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (string.IsNullOrEmpty(anchorId) || embeddedIds.Contains(anchorId)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    // If a component has no ID, or it's a child of a GameObject, skip it. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    // Children will be added via their parent GameObject. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                var gameObjectData = (JObject)dataBlock["GameObject"]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                var componentList = (JArray)gameObjectData["m_Component"]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                if (componentList == null) continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                // Get the trimmed version of the current component 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (!trimmedComponentMap.TryGetValue(anchorId, out var trimmedComponent)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    // Should not happen, but as a safeguard, add the original. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    finalOutput.Add(originalComponent.DeepClone()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                var newComponentList = new JArray(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                foreach (var compRef in componentList.Children<JObject>()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                // If it's a GameObject, embed its already-trimmed children 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (originalComponent["data"]?["GameObject"] is JObject originalGameObjectData) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    var fileID = compRef["component"]?["fileID"]?.ToString(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    if (fileID != null && componentMap.TryGetValue(fileID, out var linkedComponent)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var trimmedGameObjectData = trimmedComponent["data"]["GameObject"] as JObject; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    var componentList = originalGameObjectData["m_Component"] as JArray; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if (trimmedGameObjectData != null && componentList != null) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        // Add a clone of the linked component's 'data' object 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        newComponentList.Add(linkedComponent["data"].DeepClone()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        var newComponentList = new JArray(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        var gameObjectName = originalGameObjectData["m_Name"]?.ToString() ?? "Unnamed"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        foreach (var compRef in componentList.Children<JObject>()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            var fileID = compRef["component"]?["fileID"]?.ToString(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            if (string.IsNullOrEmpty(fileID)) continue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            if (trimmedComponentMap.TryGetValue(fileID, out var linkedComponent)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                var clonedData = linkedComponent["data"].DeepClone() as JObject; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                (clonedData?.Properties().FirstOrDefault()?.Value as JObject)?.Remove("m_GameObject"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                newComponentList.Add(clonedData); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            else 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                Debug.LogWarning($"JsonDataTrimmer: Could not find component with anchor_id '{fileID}' referenced by GameObject '{gameObjectName}'."); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        trimmedGameObjectData["m_Component"] = newComponentList; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                // Replace the old list of fileIDs with the new list of embedded data 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                gameObjectData["m_Component"] = newComponentList; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                finalOutput.Add(component); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                finalOutput.Add(trimmedComponent); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             return finalOutput.ToString(Newtonsoft.Json.Formatting.None); 
			 |