383 lines
16 KiB
C#
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);
|
|
}
|
|
}
|
|
} |