#nullable enable using Cysharp.Threading.Tasks; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using UnityEngine; using UVC.Extention; namespace UVC.Data.Core { /// /// JSON 데이터의 구조와 변환 규칙을 정의하는 마스크 클래스입니다. /// Dictionary를 상속하여 JSON 데이터 구조를 표현하고, /// 데이터 매핑, 필드 이름 변환, 그리고 타입 변환을 위한 메타데이터를 제공합니다. /// /// /// DataMask는 주로 DataMapper 클래스와 함께 사용되어 서로 다른 JSON 형식 간의 /// 데이터 변환 및 매핑 규칙을 정의합니다. 기본 JSON 구조 외에도 추가 속성들을 /// 통해 매핑 과정에서 필요한 메타데이터를 제공합니다. /// 또한 AppData 폴더(AppData/Company Name/Product Name)에서 JSON 파일을 로드하고 저장하는 기능을 제공하여, /// 그 기능은 FactoryObject의 정보를 InfoWindow로 보여줄때 사용됩니다. /// /// /// 기본 사용 예시: /// /// // 마스크 객체 생성 및 설정 /// var mask = new DataMask(); /// mask["name"] = ""; // 문자열 타입 지정 /// mask["age"] = 0; // 정수 타입 지정 /// mask["isActive"] = false; // 불리언 타입 지정 /// mask["height"] = 0.0; // 실수 타입 지정 /// /// // 메타 속성 설정 /// mask.ObjectIdKey = "name"; // DataObject의 ID로 사용할 필드 지정 /// mask.ObjectName = "employees"; // 데이터 객체의 이름 지정 /// /// // 필드 이름 변환 규칙 설정 /// mask.NamesForReplace = new Dictionary /// { /// { "full_name", "name" }, // JSON의 full_name을 name으로 변환 /// { "employee_age", "age" } // JSON의 employee_age를 age로 변환 /// }; /// /// // 매핑 예시 (DataMapper 클래스와 함께 사용) /// var sourceJson = JObject.Parse(@"{ /// ""full_name"": ""김철수"", /// ""employee_age"": 30, /// ""isActive"": true, /// ""height"": 175.5 /// }"); /// /// var mapper = new DataMapper(mask); /// DataObject result = mapper.Mapping(sourceJson); /// // result는 변환된 필드 이름과 타입을 가진 DataObject 객체 /// /// public class DataMask : OrderedDictionary { private static Dictionary _dataMasks = new Dictionary(); public static IReadOnlyDictionary DataMasks => _dataMasks; /// /// 컬렉션에 데이터 마스크를 추가하고 지정된 키와 연결합니다. /// /// 컬렉션에 동일한 키를 가진 마스크가 이미 있는 경우, 새 마스크로 대체됩니다. /// /// 데이터 마스크에 연결할 키입니다. 이거나 비어 있을 수 없습니다. /// 추가할 입니다. 일 수 없습니다. /// 사용자 데이터 마스크도 함께 추가할지 여부를 지정합니다. 기본값은 false입니다. public static void AddMask(string key, DataMask mask) { if (string.IsNullOrEmpty(key) || mask == null) return; _dataMasks[key] = mask; } /// /// 지정된 키와 연관된 데이터 마스크를 제거합니다. /// /// 가 null이거나 비어 있으면 메서드는 아무 작업도 수행하지 않습니다. /// 제거할 데이터 마스크를 식별하는 키입니다. null이거나 비어 있으면 안 됩니다. /// 사용자 데이터 마스크도 함께 제거할지 여부를 지정합니다. 기본값은 false입니다. public static void RemoveMask(string key) { if (string.IsNullOrEmpty(key)) return; _dataMasks.Remove(key); } /// /// 지정된 키와 연관된 를 검색합니다. /// /// 를 찾는 데 사용되는 키입니다. null이거나 비어 있을 수 없습니다. /// 지정된 키와 연관된 또는 키를 찾을 수 없는 경우 을 반환합니다. /// public static DataMask? Get(string key) { if (string.IsNullOrEmpty(key)) return null; _dataMasks.TryGetValue(key, out var mask); return mask; } /// /// 내부 컬렉션에서 모든 데이터 마스크를 지웁니다. /// /// 이 메서드는 내부 데이터 마스크 컬렉션에서 모든 항목을 제거하고 /// 빈 상태로 재설정합니다. 이전에 추가된 모든 마스크를 지워야 할 때 이 메서드를 사용하세요. public static void ClearMask() { _dataMasks.Clear(); } /// /// DataObject의 Id에 해당하는 key 문자열입니다. /// 이 속성은 매핑된 DataObject에서 고유 식별자로 사용될 필드를 지정합니다. /// null인 경우 DataObject의 기본 동작을 따릅니다. /// /// /// /// var mask = new DataMask(); /// mask.ObjectIdKey = "userId"; // DataObject에서 "userId" 필드가 Id로 사용됨 /// /// public string? ObjectIdKey { get; set; } = null; /// /// DataObject의 이름을 나타내는 속성입니다. DataRepository에서 사용됩니다. /// 이 이름은 매핑된 DataObject를 분류하거나 식별하는 데 사용될 수 있습니다. /// /// /// /// var mask = new DataMask(); /// mask.ObjectName = "users"; // 매핑된 DataObject는 "users"라는 이름을 가짐 /// /// public string ObjectName { get; set; } = string.Empty; /// /// 원본 JSON에서 매핑된 DataObject로 필드 이름을 변환하는 규칙을 정의하는 딕셔너리입니다. /// 키는 원본 JSON의 필드 이름이고, 값은 변환될 대상 필드 이름입니다. /// /// /// 이 속성을 통해 서로 다른 명명 규칙을 사용하는 JSON 구조 간의 매핑을 용이하게 할 수 있습니다. /// 예를 들어, snake_case를 사용하는 API 응답을 camelCase로 변환하는 데 사용할 수 있습니다. /// /// /// /// var mask = new DataMask(); /// mask.NamesForReplace = new Dictionary /// { /// { "first_name", "firstName" }, /// { "last_name", "lastName" }, /// { "birth_date", "birthDate" } /// }; /// /// public Dictionary? NamesForReplace { get; set; } public DataMask() { } public DataMask(string jsonString) { if (string.IsNullOrEmpty(jsonString)) return; JObject jObj = JObject.Parse(jsonString); foreach (var property in jObj.Properties()) { this[property.Name] = ConvertJTokenToObject(property); } } public DataMask(JObject jObj) { if (jObj == null) return; foreach (var property in jObj.Properties()) { this[property.Name] = ConvertJTokenToObject(property.Value); } } /// /// JToken을 실제 객체로 변환하는 헬퍼 메서드 /// /// 변환할 JToken 객체 /// 변환된 .NET 객체 /// /// 이 메서드는 JToken의 타입에 따라 적절한 .NET 타입의 객체로 변환합니다. /// 객체는 DataObject로, 배열은 DataArray로 변환되며, 기본 타입은 해당하는 .NET 타입으로 변환됩니다. /// private object ConvertJTokenToObject(JToken token) { if (token == null) return null; switch (token.Type) { case JTokenType.Property: JProperty prop = (JProperty)token; return ConvertJTokenToObject(prop.Value); case JTokenType.Object: return new DataMask((JObject)token); case JTokenType.Array: JArray array = (JArray)token; List dataMasks = new List(); if (array.All(item => item.Type == JTokenType.Object)) { foreach (var item in array) { dataMasks.Add(new DataMask((JObject)item)); } } return dataMasks; case JTokenType.Integer: return token.ToObject(); case JTokenType.Float: return token.ToObject(); case JTokenType.String: return token.ToObject(); case JTokenType.Boolean: return token.ToObject(); case JTokenType.Date: return token.ToObject(); case JTokenType.Null: return null; default: return token.ToObject(); } } /// /// JObject 타입과의 호환성을 위한 메서드입니다. /// Dictionary에 저장된 모든 속성을 JObject 형식으로 반환합니다. /// /// 현재 DataMask의 내용을 담은 JObject public JObject ToJObject() { JObject result = new JObject(); foreach (var pair in this) { if (pair.Value is DataMask) { result[pair.Key] = ((DataMask)pair.Value).ToJObject(); } else { result[pair.Key] = JToken.FromObject(pair.Value); } } return result; } /// /// JObject에서 속성을 가져오기 위한 확장 메서드입니다. /// /// 속성을 추출할 JObject /// JObject의 모든 속성 private IEnumerable> GetProperties(JObject jObj) { foreach (var property in jObj.Properties()) { yield return new KeyValuePair(property.Name, property.Value); } } /// /// 다른 DataMask 객체의 속성을 현재 객체로 복사합니다. /// /// 복사할 원본 DataMask 객체 /// true인 경우 메타데이터 속성만 복사하고 데이터 필드는 복사하지 않음 /// /// maskPropertyOnly가 true인 경우 ObjectIdKey, ObjectName, NamesForReplace와 같은 /// 메타데이터 속성만 복사되고, 실제 데이터 필드(Dictionary의 요소)는 복사되지 않습니다. /// false인 경우 모든 속성과 데이터 필드가 복사됩니다. /// /// /// /// var sourceMask = new DataMask(); /// sourceMask.ObjectIdKey = "Id"; /// sourceMask["name"] = ""; /// sourceMask["age"] = 0; /// /// var targetMask = new DataMask(); /// // 모든 속성 복사 /// targetMask.CopyFrom(sourceMask); /// /// // 메타데이터만 복사 /// var metaOnlyMask = new DataMask(); /// metaOnlyMask.CopyFrom(sourceMask, true); /// /// public void CopyFrom(DataMask other, bool maskPropertyOnly = false) { if (other == null) return; this.ObjectIdKey = other.ObjectIdKey; this.ObjectName = other.ObjectName; this.NamesForReplace = other.NamesForReplace != null ? new Dictionary(other.NamesForReplace) : null; if (maskPropertyOnly) return; foreach (var pair in other) { this[pair.Key] = pair.Value; } } /// /// JObject의 속성을 현재 DataMask 객체로 복사합니다. /// /// 복사할 원본 JObject /// /// 이 메서드는 JObject의 각 속성을 적절한 .NET 객체로 변환하여 /// DataMask에 추가합니다. JObject에서 데이터를 가져오는 데 유용합니다. /// /// /// /// var jsonObject = JObject.Parse(@"{ /// ""name"": ""김철수"", /// ""age"": 30, /// ""isActive"": true /// }"); /// /// var mask = new DataMask(); /// mask.CopyFrom(jsonObject); /// // mask에는 jsonObject의 모든 속성이 복사됨 /// /// public void CopyFrom(JObject other) { if (other == null) return; foreach (var property in other.Properties()) { this[property.Name] = property.Value.ToObject(); } } /// /// 현재 DataMask 객체의 깊은 복사본을 생성합니다. /// /// 현재 객체의 모든 값과 메타데이터를 포함한 새로운 DataMask 객체 /// /// 이 메서드는 현재 DataMask의 모든 속성(ObjectIdKey, ObjectName, NamesForReplace)과 /// 모든 데이터 필드를 새 DataMask 인스턴스에 복사합니다. 복사된 객체는 원본과 /// 독립적으로 수정할 수 있습니다. /// /// /// /// var originalMask = new DataMask(); /// originalMask.ObjectIdKey = "Id"; /// originalMask.ObjectName = "users"; /// originalMask["name"] = ""; /// originalMask["age"] = 0; /// /// // 깊은 복사 생성 /// var copiedMask = originalMask.DeepClone(); /// /// // 원본에 영향 없이 복사본 수정 가능 /// copiedMask.ObjectName = "employees"; /// copiedMask["salary"] = 0.0; /// /// public DataMask DeepClone() { DataMask mask = new DataMask(); mask.ObjectIdKey = ObjectIdKey; mask.ObjectName = ObjectName; mask.NamesForReplace = NamesForReplace != null ? new Dictionary(NamesForReplace) : null; foreach (var pair in this) { mask[pair.Key] = pair.Value; } return mask; } /// /// Json 문자열로 변환합니다. /// /// public string ToJsonString() { return ToJObject().ToString(Newtonsoft.Json.Formatting.Indented); } } }