data pipeline 개발
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace UVC.Data
|
||||
{
|
||||
@@ -7,7 +8,7 @@ namespace UVC.Data
|
||||
/// 서로 다른 JSON 데이터 구조 간에 매핑 기능을 제공하는 클래스입니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 이 클래스는 JSON 데이터를 원하는 형식으로 변환하거나, 중첩된 구조(nested structure)도 처리할 수 있습니다.
|
||||
/// 이 클래스는 JSON 데이터를 DataObject와 DataArray 형식으로 변환하며, 중첩된 구조(nested structure)도 처리할 수 있습니다.
|
||||
/// 소스 JSON 객체의 속성을 가이드 객체에 정의된 타입에 따라 적절히 변환합니다.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
@@ -21,63 +22,93 @@ namespace UVC.Data
|
||||
/// }");
|
||||
///
|
||||
/// // 가이드 객체 (타입 지정용)
|
||||
/// var guideJson = JObject.Parse(@"{
|
||||
/// var maskJson = JObject.Parse(@"{
|
||||
/// ""name"": """",
|
||||
/// ""age"": 0,
|
||||
/// ""isActive"": false
|
||||
/// }");
|
||||
///
|
||||
/// var mapper = new DataMapper(sourceJson, guideJson);
|
||||
/// JObject result = mapper.Map();
|
||||
/// var mapper = new DataMapper(maskJson);
|
||||
/// DataObject result = mapper.Map(sourceJson);
|
||||
///
|
||||
/// // result는 원본과 동일한 구조이며 각 속성이 가이드에 따라 타입 변환됨
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class DataMapper
|
||||
{
|
||||
private JObject source;
|
||||
private JObject guide;
|
||||
private JObject mask;
|
||||
|
||||
/// <summary>
|
||||
/// DataMapper 클래스의 새 인스턴스를 초기화합니다.
|
||||
/// </summary>
|
||||
/// <param name="source">매핑할 원본 JSON 객체</param>
|
||||
/// <param name="guide">타입 변환을 위한 가이드 JSON 객체</param>
|
||||
/// <param name="mask">타입 변환을 위한 가이드 JSON 객체</param>
|
||||
/// <remarks>
|
||||
/// 가이드 객체는 원본 JSON 객체와 동일한 구조를 가질 필요는 없지만,
|
||||
/// 변환하려는 속성들에 대한 타입 정보를 제공해야 합니다.
|
||||
/// </remarks>
|
||||
public DataMapper(JObject source, JObject target)
|
||||
public DataMapper(JObject mask)
|
||||
{
|
||||
this.source = source;
|
||||
this.guide = target;
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 소스 객체를 가이드 객체를 기반으로 매핑하여 새로운 JSON 객체를 생성합니다.
|
||||
/// 소스 객체를 가이드 객체를 기반으로 매핑하여 새로운 DataObject를 생성합니다.
|
||||
/// </summary>
|
||||
/// <returns>매핑된 JSON 객체</returns>
|
||||
/// <param name="source">매핑할 원본 JSON 객체</param>
|
||||
/// <returns>매핑된 DataObject 객체</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var mapper = new DataMapper(sourceJson, guideJson);
|
||||
/// JObject result = mapper.Map();
|
||||
/// var mapper = new DataMapper(maskJson);
|
||||
/// DataObject result = mapper.Map(sourceJson);
|
||||
/// Debug.Log(result["name"].ToString()); // "김철수"
|
||||
/// Debug.Log(result["age"].ToObject<int>()); // 30
|
||||
/// </code>
|
||||
/// </example>
|
||||
public JObject Map()
|
||||
public DataObject Map(JObject source)
|
||||
{
|
||||
return MapObject(source, guide);
|
||||
return MapObject(source, mask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 소스 배열을 가이드 객체를 기반으로 매핑하여 새로운 DataArray를 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="source">매핑할 원본 JSON 배열</param>
|
||||
/// <returns>매핑된 DataArray 객체</returns>
|
||||
/// <remarks>
|
||||
/// 이 메서드는 가이드 객체로부터 새로운 배열을 생성하여 소스 배열의 각 항목을 매핑합니다.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var sourceArray = JArray.Parse(@"[
|
||||
/// { ""name"": ""김철수"", ""age"": 30 },
|
||||
/// { ""name"": ""이영희"", ""age"": 25 }
|
||||
/// ]");
|
||||
///
|
||||
/// var maskJson = JObject.Parse(@"{
|
||||
/// ""name"": """",
|
||||
/// ""age"": 0
|
||||
/// }");
|
||||
///
|
||||
/// var mapper = new DataMapper(maskJson);
|
||||
/// DataArray result = mapper.Map(sourceArray);
|
||||
/// // result는 원본 배열과 동일한 구조의 DataArray
|
||||
/// </code>
|
||||
/// </example>
|
||||
public DataArray Map(JArray source)
|
||||
{
|
||||
JArray arr = new JArray(mask);
|
||||
return MapArray(source, arr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 객체를 재귀적으로 매핑합니다.
|
||||
/// </summary>
|
||||
/// <param name="sourceObject">원본 JSON 객체</param>
|
||||
/// <param name="guideObject">가이드 JSON 객체</param>
|
||||
/// <returns>매핑된 JSON 객체</returns>
|
||||
/// <param name="maskObject">가이드 JSON 객체</param>
|
||||
/// <returns>매핑된 DataObject 객체</returns>
|
||||
/// <remarks>
|
||||
/// 이 메서드는 중첩된 객체와 배열을 포함하여 JSON 구조를 재귀적으로 처리합니다.
|
||||
/// 가이드 객체에 포함되지 않은 속성은 원본 값을 그대로 사용합니다.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// 중첩 객체 매핑 예시:
|
||||
@@ -92,7 +123,7 @@ namespace UVC.Data
|
||||
/// }
|
||||
/// }");
|
||||
///
|
||||
/// var guideJson = JObject.Parse(@"{
|
||||
/// var maskJson = JObject.Parse(@"{
|
||||
/// ""user"": {
|
||||
/// ""name"": """",
|
||||
/// ""address"": {
|
||||
@@ -102,53 +133,90 @@ namespace UVC.Data
|
||||
/// }
|
||||
/// }");
|
||||
///
|
||||
/// var mapper = new DataMapper(sourceJson, guideJson);
|
||||
/// var result = mapper.Map();
|
||||
/// var mapper = new DataMapper(maskJson);
|
||||
/// var result = mapper.Map(sourceJson);
|
||||
/// // result는 sourceJson과 동일한 중첩 구조를 유지
|
||||
/// </code>
|
||||
/// </example>
|
||||
private JObject MapObject(JObject sourceObject, JObject guideObject)
|
||||
private DataObject MapObject(JObject sourceObject, JObject maskObject)
|
||||
{
|
||||
JObject target = new JObject();
|
||||
DataObject target = new DataObject();
|
||||
foreach (var property in sourceObject.Properties())
|
||||
{
|
||||
if (guideObject.ContainsKey(property.Name))
|
||||
if (maskObject.ContainsKey(property.Name))
|
||||
{
|
||||
JToken guideValue = guideObject[property.Name];
|
||||
JToken maskValue = maskObject[property.Name];
|
||||
JToken sourceValue = property.Value;
|
||||
|
||||
// 중첩된 객체 처리
|
||||
if (sourceValue.Type == JTokenType.Object && guideValue.Type == JTokenType.Object)
|
||||
if (sourceValue.Type == JTokenType.Object && maskValue.Type == JTokenType.Object)
|
||||
{
|
||||
target[property.Name] = MapObject((JObject)sourceValue, (JObject)guideValue);
|
||||
target[property.Name] = MapObject((JObject)sourceValue, (JObject)maskValue);
|
||||
}
|
||||
// 중첩된 배열 처리
|
||||
else if (sourceValue.Type == JTokenType.Array && guideValue.Type == JTokenType.Array)
|
||||
else if (sourceValue.Type == JTokenType.Array && maskValue.Type == JTokenType.Array)
|
||||
{
|
||||
target[property.Name] = MapArray((JArray)sourceValue, (JArray)guideValue);
|
||||
var arr = MapArray((JArray)sourceValue, (JArray)maskValue);
|
||||
target[property.Name] = arr;
|
||||
}
|
||||
else
|
||||
{
|
||||
MapProperty(property.Name, sourceValue, guideValue, target);
|
||||
MapProperty(property.Name, sourceValue, maskValue, target);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
target[property.Name] = property.Value;
|
||||
continue; // 가이드에 없는 속성은 무시
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JToken을 실제 객체로 변환하는 헬퍼 메서드
|
||||
/// </summary>
|
||||
private object ConvertJTokenToObject(JToken token)
|
||||
{
|
||||
switch (token.Type)
|
||||
{
|
||||
case JTokenType.Object:
|
||||
return new DataObject((JObject)token);
|
||||
case JTokenType.Array:
|
||||
JArray array = (JArray)token;
|
||||
if (array.All(item => item.Type == JTokenType.Object))
|
||||
{
|
||||
return new DataArray((JArray)token);
|
||||
}
|
||||
return array.ToObject<object>();
|
||||
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>
|
||||
/// 배열을 재귀적으로 매핑합니다.
|
||||
/// </summary>
|
||||
/// <param name="sourceArray">원본 JSON 배열</param>
|
||||
/// <param name="guideArray">가이드 JSON 배열</param>
|
||||
/// <returns>매핑된 JSON 배열</returns>
|
||||
/// <param name="maskArray">가이드 JSON 배열</param>
|
||||
/// <returns>매핑된 DataArray 객체</returns>
|
||||
/// <remarks>
|
||||
/// 가이드 배열이 비어있으면, 원본 배열을 그대로 복사합니다.
|
||||
/// 그렇지 않으면, 가이드 배열의 첫 번째 항목을 템플릿으로 사용하여 원본 배열의 각 항목을 매핑합니다.
|
||||
/// 가이드 배열이 비어있으면, 원본 배열의 각 항목을 DataObject로 변환하여 복사합니다.
|
||||
/// 단순 값(문자열, 숫자 등)은 "value" 키를 가진 DataObject로 래핑됩니다.
|
||||
///
|
||||
/// 가이드 배열이 비어있지 않으면, 가이드 배열의 첫 번째 항목을 템플릿으로 사용하여
|
||||
/// 원본 배열의 각 항목을 매핑합니다. 중첩 배열은 "items" 키를 가진 DataObject로 래핑됩니다.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// 배열 매핑 예시:
|
||||
@@ -160,44 +228,78 @@ namespace UVC.Data
|
||||
/// ]
|
||||
/// }");
|
||||
///
|
||||
/// var guideJson = JObject.Parse(@"{
|
||||
/// var maskJson = JObject.Parse(@"{
|
||||
/// ""contacts"": [
|
||||
/// { ""type"": """", ""number"": """" }
|
||||
/// ]
|
||||
/// }");
|
||||
///
|
||||
/// var mapper = new DataMapper(sourceJson, guideJson);
|
||||
/// var result = mapper.Map();
|
||||
/// // result.contacts는 원본 배열과 동일한 구조의 배열
|
||||
/// var mapper = new DataMapper(maskJson);
|
||||
/// var result = mapper.Map(sourceJson);
|
||||
/// // result.contacts는 원본 배열과 동일한 구조의 DataArray
|
||||
/// </code>
|
||||
/// </example>
|
||||
private JArray MapArray(JArray sourceArray, JArray guideArray)
|
||||
private DataArray MapArray(JArray sourceArray, JArray maskArray)
|
||||
{
|
||||
JArray targetArray = new JArray();
|
||||
DataArray targetArray = new DataArray();
|
||||
|
||||
// 가이드 배열이 비어있으면 원본 배열을 그대로 사용
|
||||
if (guideArray.Count == 0)
|
||||
if (maskArray.Count == 0)
|
||||
{
|
||||
return new JArray(sourceArray);
|
||||
foreach (JToken sourceItem in sourceArray)
|
||||
{
|
||||
if (sourceItem.Type == JTokenType.Object)
|
||||
{
|
||||
targetArray.Add(new DataObject((JObject)sourceItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
// DataObject가 아닌 경우, 새 DataObject를 만들고 값을 넣어줍니다
|
||||
var dataObject = new DataObject();
|
||||
dataObject.Add("value", ConvertJTokenToObject(sourceItem));
|
||||
targetArray.Add(dataObject);
|
||||
}
|
||||
}
|
||||
return targetArray;
|
||||
}
|
||||
|
||||
// 가이드 배열의 첫 번째 항목을 템플릿으로 사용
|
||||
JToken guideTemplate = guideArray.First;
|
||||
|
||||
// 가이드 배열 개수가 1개면 1개만 템플릿으로 사용
|
||||
JToken maskTemplate = maskArray.First;
|
||||
int idx = 0;
|
||||
foreach (JToken sourceItem in sourceArray)
|
||||
{
|
||||
if (sourceItem.Type == JTokenType.Object && guideTemplate.Type == JTokenType.Object)
|
||||
if (maskArray.Count > 1)
|
||||
{
|
||||
targetArray.Add(MapObject((JObject)sourceItem, (JObject)guideTemplate));
|
||||
if (idx > 0 && idx < maskArray.Count)
|
||||
{
|
||||
maskTemplate = maskArray[idx];
|
||||
}
|
||||
else if (idx >= maskArray.Count)
|
||||
{
|
||||
continue; // 가이드 배열의 범위를 벗어나면 무시
|
||||
}
|
||||
}
|
||||
else if (sourceItem.Type == JTokenType.Array && guideTemplate.Type == JTokenType.Array)
|
||||
|
||||
if (sourceItem.Type == JTokenType.Object && maskTemplate.Type == JTokenType.Object)
|
||||
{
|
||||
targetArray.Add(MapArray((JArray)sourceItem, (JArray)guideTemplate));
|
||||
targetArray.Add(MapObject((JObject)sourceItem, (JObject)maskTemplate));
|
||||
}
|
||||
else if (sourceItem.Type == JTokenType.Array && maskTemplate.Type == JTokenType.Array)
|
||||
{
|
||||
// DataArray는 DataObject만 담을 수 있으므로, 배열을 처리하는 방식을 변경해야 함
|
||||
var nestedArray = MapArray((JArray)sourceItem, (JArray)maskTemplate);
|
||||
var container = new DataObject();
|
||||
container.Add("items", nestedArray);
|
||||
targetArray.Add(container);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetArray.Add(sourceItem);
|
||||
// 단순 값을 DataObject로 래핑
|
||||
var dataObject = new DataObject();
|
||||
dataObject.Add("value", ConvertJTokenToObject(sourceItem));
|
||||
targetArray.Add(dataObject);
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
return targetArray;
|
||||
@@ -208,11 +310,21 @@ namespace UVC.Data
|
||||
/// </summary>
|
||||
/// <param name="propertyName">속성 이름</param>
|
||||
/// <param name="sourceValue">매핑할 원본 값</param>
|
||||
/// <param name="guideValue">타입을 결정하는 가이드 값</param>
|
||||
/// <param name="target">값을 추가할 대상 객체</param>
|
||||
/// <param name="maskValue">타입을 결정하는 가이드 값</param>
|
||||
/// <param name="target">값을 추가할 대상 DataObject</param>
|
||||
/// <remarks>
|
||||
/// 이 메서드는 가이드 값의 타입에 따라 원본 값을 적절한 타입으로 변환합니다.
|
||||
/// 지원되는 타입: 문자열, 정수, 실수, 불리언, 날짜/시간, 열거형, DataValueMapper
|
||||
///
|
||||
/// 지원되는 타입:
|
||||
/// - 문자열 (JTokenType.String)
|
||||
/// - 정수 (JTokenType.Integer)
|
||||
/// - 실수 (JTokenType.Float)
|
||||
/// - 불리언 (JTokenType.Boolean)
|
||||
/// - 날짜/시간 (JTokenType.Date)
|
||||
/// - 열거형 (Enum)
|
||||
/// - DataValueMapper (문자열 매핑 딕셔너리)
|
||||
///
|
||||
/// 타입 변환이 불가능한 경우 원본 값이 그대로 사용됩니다.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// 다양한 타입 매핑 예시:
|
||||
@@ -227,89 +339,91 @@ namespace UVC.Data
|
||||
/// }");
|
||||
///
|
||||
/// // 가이드 객체 설정 (열거형 포함)
|
||||
/// var guideJson = new JObject();
|
||||
/// guideJson["name"] = "";
|
||||
/// guideJson["age"] = 0;
|
||||
/// guideJson["height"] = 0.0;
|
||||
/// guideJson["isActive"] = false;
|
||||
/// guideJson["birthDate"] = JToken.FromObject(DateTime.Now);
|
||||
/// guideJson["status"] = JToken.FromObject(UserStatus.Inactive);
|
||||
/// var maskJson = new JObject();
|
||||
/// maskJson["name"] = "";
|
||||
/// maskJson["age"] = 0;
|
||||
/// maskJson["height"] = 0.0;
|
||||
/// maskJson["isActive"] = false;
|
||||
/// maskJson["birthDate"] = JToken.FromObject(DateTime.Now);
|
||||
/// maskJson["status"] = JToken.FromObject(UserStatus.Inactive);
|
||||
///
|
||||
/// var mapper = new DataMapper(sourceJson, guideJson);
|
||||
/// var result = mapper.Map();
|
||||
/// var mapper = new DataMapper(maskJson);
|
||||
/// var result = mapper.Map(sourceJson);
|
||||
/// // result에는 모든 속성이 적절한 타입으로 변환됨
|
||||
/// </code>
|
||||
/// </example>
|
||||
private void MapProperty(string propertyName, JToken sourceValue, JToken guideValue, JObject target)
|
||||
private void MapProperty(string propertyName, JToken sourceValue, JToken maskValue, DataObject target)
|
||||
{
|
||||
if (guideValue.Type == JTokenType.String && sourceValue.Type == JTokenType.String)
|
||||
if (maskValue.Type == JTokenType.String && sourceValue.Type == JTokenType.String)
|
||||
{
|
||||
target[propertyName] = sourceValue.ToObject<string>();
|
||||
}
|
||||
else if (guideValue.Type == JTokenType.Integer && sourceValue.Type == JTokenType.Integer)
|
||||
else if (maskValue.Type == JTokenType.Integer && sourceValue.Type == JTokenType.Integer)
|
||||
{
|
||||
target[propertyName] = sourceValue.ToObject<int>();
|
||||
}
|
||||
else if (guideValue.Type == JTokenType.Float && sourceValue.Type == JTokenType.Float)
|
||||
else if (maskValue.Type == JTokenType.Float && sourceValue.Type == JTokenType.Float)
|
||||
{
|
||||
target[propertyName] = sourceValue.ToObject<double>();
|
||||
}
|
||||
else if (guideValue.Type == JTokenType.Boolean && sourceValue.Type == JTokenType.Boolean)
|
||||
else if (maskValue.Type == JTokenType.Boolean && sourceValue.Type == JTokenType.Boolean)
|
||||
{
|
||||
target[propertyName] = sourceValue.ToObject<bool>();
|
||||
}
|
||||
else if (guideValue.Type == JTokenType.Date && sourceValue.Type == JTokenType.String)
|
||||
else if (maskValue.Type == JTokenType.Date && sourceValue.Type == JTokenType.String)
|
||||
{
|
||||
string dateStr = sourceValue.ToObject<string>();
|
||||
if (DateTime.TryParse(dateStr, out DateTime dateValue))
|
||||
{
|
||||
target[propertyName] = JToken.FromObject(dateValue);
|
||||
target[propertyName] = dateValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
target[propertyName] = null;
|
||||
}
|
||||
}
|
||||
else if (guideValue.ToObject<object>()?.GetType()?.IsEnum == true && sourceValue.Type == JTokenType.String)
|
||||
else if (maskValue.ToObject<object>()?.GetType()?.IsEnum == true && sourceValue.Type == JTokenType.String)
|
||||
{
|
||||
Type enumType = guideValue.ToObject<object>().GetType();
|
||||
target[propertyName] = JToken.FromObject(Enum.Parse(enumType, sourceValue.ToObject<string>(), true));
|
||||
Type enumType = maskValue.ToObject<object>().GetType();
|
||||
target[propertyName] = Enum.Parse(enumType, sourceValue.ToObject<string>(), true);
|
||||
}
|
||||
else if (guideValue.Type == JTokenType.Object && sourceValue.Type == JTokenType.String)
|
||||
else if (maskValue.Type == JTokenType.Object && sourceValue.Type == JTokenType.String)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 먼저 DataValueMapper로 변환 시도
|
||||
var dataValueMapper = guideValue.ToObject<DataValueMapper>();
|
||||
var dataValueMapper = maskValue.ToObject<DataValueMapper>();
|
||||
if (dataValueMapper != null)
|
||||
{
|
||||
string strValue = sourceValue.ToObject<string>();
|
||||
if (dataValueMapper.ContainsKey(strValue))
|
||||
{
|
||||
target[propertyName] = new JValue(dataValueMapper[strValue]);
|
||||
target[propertyName] = dataValueMapper[strValue];
|
||||
}
|
||||
else
|
||||
{
|
||||
target[propertyName] = new JValue(strValue);
|
||||
target[propertyName] = strValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// DataValueMapper가 아니면 소스 값 그대로 사용
|
||||
target[propertyName] = sourceValue;
|
||||
target[propertyName] = sourceValue.ToObject<object>();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 변환 실패 시 소스 값 그대로 사용
|
||||
target[propertyName] = sourceValue;
|
||||
target[propertyName] = sourceValue.ToObject<object>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
target[propertyName] = sourceValue;
|
||||
// 기타 타입은 그대로 객체로 변환
|
||||
target[propertyName] = ConvertJTokenToObject(sourceValue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user