Files
XRLib/Assets/Scripts/UVC/Data/Core/DataMask.cs
2025-09-26 18:08:07 +09:00

383 lines
16 KiB
C#

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