using System.Collections.Generic; using System.Linq; using AssetBank.Settings; using Newtonsoft.Json.Linq; using UnityEngine; namespace AssetBank.Editor.Tools { public static class JsonDataTrimmer { public static string Process(string rawJsonText, ProjectExporterSettings settings) { var allComponents = JArray.Parse(rawJsonText); // 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: Create a fully trimmed copy of every component var trimmedComponentMap = new Dictionary(); foreach (var component in allComponents.Children()) { 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) { trimmedComponentMap[anchorId] = trimmedComponent; continue; } var componentName = componentProperty.Name; var componentJObject = componentProperty.Value as JObject; if (componentJObject == null) { trimmedComponentMap[anchorId] = trimmedComponent; continue; } if (rules.TryGetValue(componentName, out var exposedFields)) { if (exposedFields.Contains("*")) { trimmedComponentMap[anchorId] = trimmedComponent; continue; } var propertiesToKeep = new HashSet(exposedFields); var currentKeys = componentJObject.Properties().Select(p => p.Name).ToList(); foreach (var key in currentKeys) { if (!propertiesToKeep.Contains(key)) { componentJObject.Remove(key); } } } else { componentJObject.RemoveAll(); } trimmedComponentMap[anchorId] = trimmedComponent; } // Pass 3: Final Assembly var finalOutput = new JArray(); var embeddedIds = new HashSet(); // First, identify all components that will be embedded into GameObjects foreach (var originalComponent in allComponents.Children()) { if (originalComponent["data"]?["GameObject"]?["m_Component"] is JArray componentList) { foreach (var compRef in componentList.Children()) { 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()) { 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; } // 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; } // If it's a GameObject, embed its already-trimmed children if (originalComponent["data"]?["GameObject"] is JObject originalGameObjectData) { var trimmedGameObjectData = trimmedComponent["data"]["GameObject"] as JObject; var componentList = originalGameObjectData["m_Component"] as JArray; if (trimmedGameObjectData != null && componentList != null) { var newComponentList = new JArray(); var gameObjectName = originalGameObjectData["m_Name"]?.ToString() ?? "Unnamed"; foreach (var compRef in componentList.Children()) { 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; } } finalOutput.Add(trimmedComponent); } return finalOutput.ToString(Newtonsoft.Json.Formatting.None); } } }