DataMapper 중첩 구조 처리
This commit is contained in:
@@ -4,79 +4,132 @@ using System;
|
|||||||
namespace UVC.Data
|
namespace UVC.Data
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 서로 다른 형식이나 구조 간에 데이터를 매핑하는 기능을 제공합니다.
|
/// 서로 다른 JSON 데이터 구조 간에 매핑 기능을 제공하는 클래스입니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>이 클래스는 일반적으로 데이터베이스 레코드를 도메인 객체에 매핑하거나 서로 다른 데이터 모델 간에 변환하는 등 데이터를 한 표현에서 다른 표현으로 변환하거나 매핑하는 데 사용됩니다.
|
/// <remarks>
|
||||||
///</remarks>
|
/// 이 클래스는 JSON 데이터를 원하는 형식으로 변환하거나, 중첩된 구조(nested structure)도 처리할 수 있습니다.
|
||||||
|
/// 소스 JSON 객체의 속성을 가이드 객체에 정의된 타입에 따라 적절히 변환합니다.
|
||||||
|
/// </remarks>
|
||||||
|
/// <example>
|
||||||
|
/// 기본 사용 예시:
|
||||||
|
/// <code>
|
||||||
|
/// // 소스 JSON 데이터
|
||||||
|
/// var sourceJson = JObject.Parse(@"{
|
||||||
|
/// ""name"": ""김철수"",
|
||||||
|
/// ""age"": 30,
|
||||||
|
/// ""isActive"": true
|
||||||
|
/// }");
|
||||||
|
///
|
||||||
|
/// // 가이드 객체 (타입 지정용)
|
||||||
|
/// var guideJson = JObject.Parse(@"{
|
||||||
|
/// ""name"": """",
|
||||||
|
/// ""age"": 0,
|
||||||
|
/// ""isActive"": false
|
||||||
|
/// }");
|
||||||
|
///
|
||||||
|
/// var mapper = new DataMapper(sourceJson, guideJson);
|
||||||
|
/// JObject result = mapper.Map();
|
||||||
|
///
|
||||||
|
/// // result는 원본과 동일한 구조이며 각 속성이 가이드에 따라 타입 변환됨
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public class DataMapper
|
public class DataMapper
|
||||||
{
|
{
|
||||||
private JObject source;
|
private JObject source;
|
||||||
private JObject guide;
|
private JObject guide;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DataMapper 클래스의 새 인스턴스를 초기화합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">매핑할 원본 JSON 객체</param>
|
||||||
|
/// <param name="guide">타입 변환을 위한 가이드 JSON 객체</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// 가이드 객체는 원본 JSON 객체와 동일한 구조를 가질 필요는 없지만,
|
||||||
|
/// 변환하려는 속성들에 대한 타입 정보를 제공해야 합니다.
|
||||||
|
/// </remarks>
|
||||||
public DataMapper(JObject source, JObject target)
|
public DataMapper(JObject source, JObject target)
|
||||||
{
|
{
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.guide = target;
|
this.guide = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 소스 객체를 가이드 객체를 기반으로 매핑하여 새로운 JSON 객체를 생성합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>매핑된 JSON 객체</returns>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// var mapper = new DataMapper(sourceJson, guideJson);
|
||||||
|
/// JObject result = mapper.Map();
|
||||||
|
/// Debug.Log(result["name"].ToString()); // "김철수"
|
||||||
|
/// Debug.Log(result["age"].ToObject<int>()); // 30
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
public JObject Map()
|
public JObject Map()
|
||||||
|
{
|
||||||
|
return MapObject(source, guide);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 객체를 재귀적으로 매핑합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceObject">원본 JSON 객체</param>
|
||||||
|
/// <param name="guideObject">가이드 JSON 객체</param>
|
||||||
|
/// <returns>매핑된 JSON 객체</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// 이 메서드는 중첩된 객체와 배열을 포함하여 JSON 구조를 재귀적으로 처리합니다.
|
||||||
|
/// </remarks>
|
||||||
|
/// <example>
|
||||||
|
/// 중첩 객체 매핑 예시:
|
||||||
|
/// <code>
|
||||||
|
/// var sourceJson = JObject.Parse(@"{
|
||||||
|
/// ""user"": {
|
||||||
|
/// ""name"": ""김철수"",
|
||||||
|
/// ""address"": {
|
||||||
|
/// ""city"": ""서울"",
|
||||||
|
/// ""zipcode"": ""12345""
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }");
|
||||||
|
///
|
||||||
|
/// var guideJson = JObject.Parse(@"{
|
||||||
|
/// ""user"": {
|
||||||
|
/// ""name"": """",
|
||||||
|
/// ""address"": {
|
||||||
|
/// ""city"": """",
|
||||||
|
/// ""zipcode"": """"
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }");
|
||||||
|
///
|
||||||
|
/// var mapper = new DataMapper(sourceJson, guideJson);
|
||||||
|
/// var result = mapper.Map();
|
||||||
|
/// // result는 sourceJson과 동일한 중첩 구조를 유지
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
private JObject MapObject(JObject sourceObject, JObject guideObject)
|
||||||
{
|
{
|
||||||
JObject target = new JObject();
|
JObject target = new JObject();
|
||||||
foreach (var property in source.Properties())
|
foreach (var property in sourceObject.Properties())
|
||||||
{
|
{
|
||||||
if (guide.ContainsKey(property.Name))
|
if (guideObject.ContainsKey(property.Name))
|
||||||
{
|
{
|
||||||
JToken guideValue = guide[property.Name];
|
JToken guideValue = guideObject[property.Name];
|
||||||
string guideType = guideValue?.Type.ToString() ?? "null";
|
JToken sourceValue = property.Value;
|
||||||
if (guideType == "String" && property.Value.Type == JTokenType.String)
|
|
||||||
|
// 중첩된 객체 처리
|
||||||
|
if (sourceValue.Type == JTokenType.Object && guideValue.Type == JTokenType.Object)
|
||||||
{
|
{
|
||||||
target[property.Name] = property.Value.ToObject<string>();
|
target[property.Name] = MapObject((JObject)sourceValue, (JObject)guideValue);
|
||||||
}
|
}
|
||||||
else if (guideType == "Integer" && property.Value.Type == JTokenType.Integer)
|
// 중첩된 배열 처리
|
||||||
|
else if (sourceValue.Type == JTokenType.Array && guideValue.Type == JTokenType.Array)
|
||||||
{
|
{
|
||||||
target[property.Name] = property.Value.ToObject<int>();
|
target[property.Name] = MapArray((JArray)sourceValue, (JArray)guideValue);
|
||||||
}
|
|
||||||
else if (guideType == "Float" && property.Value.Type == JTokenType.Float)
|
|
||||||
{
|
|
||||||
target[property.Name] = property.Value.ToObject<double>();
|
|
||||||
}
|
|
||||||
else if (guideType == "Boolean" && property.Value.Type == JTokenType.Boolean)
|
|
||||||
{
|
|
||||||
target[property.Name] = property.Value.ToObject<bool>();
|
|
||||||
}
|
|
||||||
else if (guideType == "Object" && guideValue.ToObject<object>() is DataValueMapper && property.Value.Type == JTokenType.String)
|
|
||||||
{
|
|
||||||
string strValue = property.Value.ToObject<string>();
|
|
||||||
var dataValueMapper = guideValue.ToObject<DataValueMapper>();
|
|
||||||
if (dataValueMapper != null && dataValueMapper.ContainsKey(strValue))
|
|
||||||
{
|
|
||||||
target[property.Name] = dataValueMapper[strValue];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
target[property.Name] = strValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (guideType == "Date" && property.Value.Type == JTokenType.String)
|
|
||||||
{
|
|
||||||
string dateStr = property.Value.ToObject<string>();
|
|
||||||
if (DateTime.TryParse(dateStr, out DateTime dateValue))
|
|
||||||
{
|
|
||||||
target[property.Name] = JToken.FromObject(dateValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
target[property.Name] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (guideValue.ToObject<object>()?.GetType()?.IsEnum == true && property.Value.Type == JTokenType.String)
|
|
||||||
{
|
|
||||||
Type enumType = guideValue.ToObject<object>().GetType();
|
|
||||||
target[property.Name] = JToken.FromObject(Enum.Parse(enumType, property.Value.ToObject<string>(), true));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
target[property.Name] = property.Value;
|
MapProperty(property.Name, sourceValue, guideValue, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -87,6 +140,176 @@ namespace UVC.Data
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 배열을 재귀적으로 매핑합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceArray">원본 JSON 배열</param>
|
||||||
|
/// <param name="guideArray">가이드 JSON 배열</param>
|
||||||
|
/// <returns>매핑된 JSON 배열</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// 가이드 배열이 비어있으면, 원본 배열을 그대로 복사합니다.
|
||||||
|
/// 그렇지 않으면, 가이드 배열의 첫 번째 항목을 템플릿으로 사용하여 원본 배열의 각 항목을 매핑합니다.
|
||||||
|
/// </remarks>
|
||||||
|
/// <example>
|
||||||
|
/// 배열 매핑 예시:
|
||||||
|
/// <code>
|
||||||
|
/// var sourceJson = JObject.Parse(@"{
|
||||||
|
/// ""contacts"": [
|
||||||
|
/// { ""type"": ""mobile"", ""number"": ""010-1234-5678"" },
|
||||||
|
/// { ""type"": ""home"", ""number"": ""02-123-4567"" }
|
||||||
|
/// ]
|
||||||
|
/// }");
|
||||||
|
///
|
||||||
|
/// var guideJson = JObject.Parse(@"{
|
||||||
|
/// ""contacts"": [
|
||||||
|
/// { ""type"": """", ""number"": """" }
|
||||||
|
/// ]
|
||||||
|
/// }");
|
||||||
|
///
|
||||||
|
/// var mapper = new DataMapper(sourceJson, guideJson);
|
||||||
|
/// var result = mapper.Map();
|
||||||
|
/// // result.contacts는 원본 배열과 동일한 구조의 배열
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
private JArray MapArray(JArray sourceArray, JArray guideArray)
|
||||||
|
{
|
||||||
|
JArray targetArray = new JArray();
|
||||||
|
|
||||||
|
// 가이드 배열이 비어있으면 원본 배열을 그대로 사용
|
||||||
|
if (guideArray.Count == 0)
|
||||||
|
{
|
||||||
|
return new JArray(sourceArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 가이드 배열의 첫 번째 항목을 템플릿으로 사용
|
||||||
|
JToken guideTemplate = guideArray.First;
|
||||||
|
|
||||||
|
foreach (JToken sourceItem in sourceArray)
|
||||||
|
{
|
||||||
|
if (sourceItem.Type == JTokenType.Object && guideTemplate.Type == JTokenType.Object)
|
||||||
|
{
|
||||||
|
targetArray.Add(MapObject((JObject)sourceItem, (JObject)guideTemplate));
|
||||||
|
}
|
||||||
|
else if (sourceItem.Type == JTokenType.Array && guideTemplate.Type == JTokenType.Array)
|
||||||
|
{
|
||||||
|
targetArray.Add(MapArray((JArray)sourceItem, (JArray)guideTemplate));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetArray.Add(sourceItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 개별 속성을 매핑합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyName">속성 이름</param>
|
||||||
|
/// <param name="sourceValue">매핑할 원본 값</param>
|
||||||
|
/// <param name="guideValue">타입을 결정하는 가이드 값</param>
|
||||||
|
/// <param name="target">값을 추가할 대상 객체</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// 이 메서드는 가이드 값의 타입에 따라 원본 값을 적절한 타입으로 변환합니다.
|
||||||
|
/// 지원되는 타입: 문자열, 정수, 실수, 불리언, 날짜/시간, 열거형, DataValueMapper
|
||||||
|
/// </remarks>
|
||||||
|
/// <example>
|
||||||
|
/// 다양한 타입 매핑 예시:
|
||||||
|
/// <code>
|
||||||
|
/// var sourceJson = JObject.Parse(@"{
|
||||||
|
/// ""name"": ""김철수"",
|
||||||
|
/// ""age"": 30,
|
||||||
|
/// ""height"": 175.5,
|
||||||
|
/// ""isActive"": true,
|
||||||
|
/// ""birthDate"": ""1990-01-01T00:00:00"",
|
||||||
|
/// ""status"": ""Active""
|
||||||
|
/// }");
|
||||||
|
///
|
||||||
|
/// // 가이드 객체 설정 (열거형 포함)
|
||||||
|
/// 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 mapper = new DataMapper(sourceJson, guideJson);
|
||||||
|
/// var result = mapper.Map();
|
||||||
|
/// // result에는 모든 속성이 적절한 타입으로 변환됨
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
private void MapProperty(string propertyName, JToken sourceValue, JToken guideValue, JObject target)
|
||||||
|
{
|
||||||
|
if (guideValue.Type == JTokenType.String && sourceValue.Type == JTokenType.String)
|
||||||
|
{
|
||||||
|
target[propertyName] = sourceValue.ToObject<string>();
|
||||||
|
}
|
||||||
|
else if (guideValue.Type == JTokenType.Integer && sourceValue.Type == JTokenType.Integer)
|
||||||
|
{
|
||||||
|
target[propertyName] = sourceValue.ToObject<int>();
|
||||||
|
}
|
||||||
|
else if (guideValue.Type == JTokenType.Float && sourceValue.Type == JTokenType.Float)
|
||||||
|
{
|
||||||
|
target[propertyName] = sourceValue.ToObject<double>();
|
||||||
|
}
|
||||||
|
else if (guideValue.Type == JTokenType.Boolean && sourceValue.Type == JTokenType.Boolean)
|
||||||
|
{
|
||||||
|
target[propertyName] = sourceValue.ToObject<bool>();
|
||||||
|
}
|
||||||
|
else if (guideValue.Type == JTokenType.Date && sourceValue.Type == JTokenType.String)
|
||||||
|
{
|
||||||
|
string dateStr = sourceValue.ToObject<string>();
|
||||||
|
if (DateTime.TryParse(dateStr, out DateTime dateValue))
|
||||||
|
{
|
||||||
|
target[propertyName] = JToken.FromObject(dateValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target[propertyName] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (guideValue.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));
|
||||||
|
}
|
||||||
|
else if (guideValue.Type == JTokenType.Object && sourceValue.Type == JTokenType.String)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 먼저 DataValueMapper로 변환 시도
|
||||||
|
var dataValueMapper = guideValue.ToObject<DataValueMapper>();
|
||||||
|
if (dataValueMapper != null)
|
||||||
|
{
|
||||||
|
string strValue = sourceValue.ToObject<string>();
|
||||||
|
if (dataValueMapper.ContainsKey(strValue))
|
||||||
|
{
|
||||||
|
target[propertyName] = new JValue(dataValueMapper[strValue]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target[propertyName] = new JValue(strValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// DataValueMapper가 아니면 소스 값 그대로 사용
|
||||||
|
target[propertyName] = sourceValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 변환 실패 시 소스 값 그대로 사용
|
||||||
|
target[propertyName] = sourceValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
target[propertyName] = sourceValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,57 @@ namespace UVC.Tests.Data
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class DataMapperTests
|
public class DataMapperTests
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모든 테스트 메서드를 실행하는 메서드입니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 이 메서드는 클래스의 모든 테스트 메서드를 순차적으로 호출하고
|
||||||
|
/// 각 테스트의 성공 또는 실패 여부를 로그로 출력합니다.
|
||||||
|
/// </remarks>
|
||||||
|
public void TestAll()
|
||||||
|
{
|
||||||
|
Debug.Log("===== DataMapper 테스트 시작 =====");
|
||||||
|
|
||||||
|
RunTest(nameof(Map_StringProperty_MapsCorrectly), Map_StringProperty_MapsCorrectly);
|
||||||
|
RunTest(nameof(Map_IntProperty_MapsCorrectly), Map_IntProperty_MapsCorrectly);
|
||||||
|
RunTest(nameof(Map_DoubleProperty_MapsCorrectly), Map_DoubleProperty_MapsCorrectly);
|
||||||
|
RunTest(nameof(Map_BoolProperty_MapsCorrectly), Map_BoolProperty_MapsCorrectly);
|
||||||
|
RunTest(nameof(Map_DateTimeProperty_MapsCorrectly), Map_DateTimeProperty_MapsCorrectly);
|
||||||
|
RunTest(nameof(Map_DataValueMapperProperty_MapsCorrectly), Map_DataValueMapperProperty_MapsCorrectly);
|
||||||
|
RunTest(nameof(Map_DataValueMapperWithUnmappedValue_ReturnsOriginal), Map_DataValueMapperWithUnmappedValue_ReturnsOriginal);
|
||||||
|
RunTest(nameof(Map_EnumProperty_MapsCorrectly), Map_EnumProperty_MapsCorrectly);
|
||||||
|
RunTest(nameof(Map_AdditionalProperty_AddsToResult), Map_AdditionalProperty_AddsToResult);
|
||||||
|
RunTest(nameof(Map_InvalidDateTimeString_ReturnsNull), Map_InvalidDateTimeString_ReturnsNull);
|
||||||
|
RunTest(nameof(Map_ComplexObject_MapsAllProperties), Map_ComplexObject_MapsAllProperties);
|
||||||
|
RunTest(nameof(Map_NestedObject_MapsCorrectly), Map_NestedObject_MapsCorrectly);
|
||||||
|
RunTest(nameof(Map_ArrayMapping_MapsCorrectly), Map_ArrayMapping_MapsCorrectly);
|
||||||
|
RunTest(nameof(Map_EmptyGuideArray_CopiesSourceArray), Map_EmptyGuideArray_CopiesSourceArray);
|
||||||
|
RunTest(nameof(Map_ComplexNestedStructure_MapsCorrectly), Map_ComplexNestedStructure_MapsCorrectly);
|
||||||
|
RunTest(nameof(Map_MixedArrayTypes_HandlesCorrectly), Map_MixedArrayTypes_HandlesCorrectly);
|
||||||
|
|
||||||
|
Debug.Log("===== DataMapper 테스트 완료 =====");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 단일 테스트 메서드를 실행하고 결과를 로그로 출력합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="testName">테스트 메서드 이름</param>
|
||||||
|
/// <param name="testAction">실행할 테스트 메서드</param>
|
||||||
|
private void RunTest(string testName, Action testAction)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Debug.Log($"테스트 시작: {testName}");
|
||||||
|
testAction();
|
||||||
|
Debug.Log($"테스트 성공: {testName}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"테스트 실패: {testName}\n{ex.Message}\n{ex.StackTrace}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 문자열 타입 매핑 테스트
|
/// 문자열 타입 매핑 테스트
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -148,6 +199,31 @@ namespace UVC.Tests.Data
|
|||||||
Assert.AreEqual("활성화", result["Status"].ToString());
|
Assert.AreEqual("활성화", result["Status"].ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DataValueMapper에 매핑되지 않는 값 처리 테스트
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Map_DataValueMapperWithUnmappedValue_ReturnsOriginal()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var guide = new JObject();
|
||||||
|
var valueMapper = new DataValueMapper();
|
||||||
|
valueMapper["ON"] = "활성화";
|
||||||
|
valueMapper["OFF"] = "비활성화";
|
||||||
|
guide["Status"] = JToken.FromObject(valueMapper);
|
||||||
|
|
||||||
|
var source = JObject.Parse(@"{ ""Status"": ""UNKNOWN"" }");
|
||||||
|
|
||||||
|
var mapper = new DataMapper(source, guide);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = mapper.Map();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsTrue(result.ContainsKey("Status"));
|
||||||
|
Assert.AreEqual("UNKNOWN", result["Status"].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 열거형 타입 매핑 테스트
|
/// 열거형 타입 매핑 테스트
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -255,6 +331,276 @@ namespace UVC.Tests.Data
|
|||||||
Assert.AreEqual(UserStatus.Active, result["Status"].ToObject<UserStatus>());
|
Assert.AreEqual(UserStatus.Active, result["Status"].ToObject<UserStatus>());
|
||||||
Assert.AreEqual("kim@example.com", result["Email"].ToString());
|
Assert.AreEqual("kim@example.com", result["Email"].ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 중첩된 객체 매핑 테스트 - 새로 추가
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Map_NestedObject_MapsCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var guide = new JObject
|
||||||
|
{
|
||||||
|
["User"] = new JObject
|
||||||
|
{
|
||||||
|
["Name"] = "홍길동",
|
||||||
|
["Address"] = new JObject
|
||||||
|
{
|
||||||
|
["City"] = "서울",
|
||||||
|
["ZipCode"] = "12345"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var source = JObject.Parse(@"{
|
||||||
|
""User"": {
|
||||||
|
""Name"": ""김철수"",
|
||||||
|
""Address"": {
|
||||||
|
""City"": ""부산"",
|
||||||
|
""ZipCode"": ""67890"",
|
||||||
|
""Country"": ""대한민국""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
var mapper = new DataMapper(source, guide);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = mapper.Map();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsTrue(result.ContainsKey("User"));
|
||||||
|
var user = result["User"] as JObject;
|
||||||
|
Assert.IsNotNull(user);
|
||||||
|
Assert.AreEqual("김철수", user["Name"].ToString());
|
||||||
|
|
||||||
|
var address = user["Address"] as JObject;
|
||||||
|
Assert.IsNotNull(address);
|
||||||
|
Assert.AreEqual("부산", address["City"].ToString());
|
||||||
|
Assert.AreEqual("67890", address["ZipCode"].ToString());
|
||||||
|
Assert.AreEqual("대한민국", address["Country"].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 배열 매핑 테스트 - 새로 추가
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Map_ArrayMapping_MapsCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var guide = new JObject
|
||||||
|
{
|
||||||
|
["Contacts"] = new JArray
|
||||||
|
{
|
||||||
|
new JObject
|
||||||
|
{
|
||||||
|
["Type"] = "mobile",
|
||||||
|
["Number"] = "010-0000-0000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var source = JObject.Parse(@"{
|
||||||
|
""Contacts"": [
|
||||||
|
{ ""Type"": ""mobile"", ""Number"": ""010-1234-5678"" },
|
||||||
|
{ ""Type"": ""home"", ""Number"": ""02-123-4567"", ""Extension"": ""123"" }
|
||||||
|
]
|
||||||
|
}");
|
||||||
|
|
||||||
|
var mapper = new DataMapper(source, guide);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = mapper.Map();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsTrue(result.ContainsKey("Contacts"));
|
||||||
|
var contacts = result["Contacts"] as JArray;
|
||||||
|
Assert.IsNotNull(contacts);
|
||||||
|
Assert.AreEqual(2, contacts.Count);
|
||||||
|
|
||||||
|
var contact1 = contacts[0] as JObject;
|
||||||
|
Assert.IsNotNull(contact1);
|
||||||
|
Assert.AreEqual("mobile", contact1["Type"].ToString());
|
||||||
|
Assert.AreEqual("010-1234-5678", contact1["Number"].ToString());
|
||||||
|
|
||||||
|
var contact2 = contacts[1] as JObject;
|
||||||
|
Assert.IsNotNull(contact2);
|
||||||
|
Assert.AreEqual("home", contact2["Type"].ToString());
|
||||||
|
Assert.AreEqual("02-123-4567", contact2["Number"].ToString());
|
||||||
|
Assert.AreEqual("123", contact2["Extension"].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 빈 가이드 배열 테스트 - 새로 추가
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Map_EmptyGuideArray_CopiesSourceArray()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var guide = new JObject
|
||||||
|
{
|
||||||
|
["Tags"] = new JArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
var source = JObject.Parse(@"{
|
||||||
|
""Tags"": [""개발"", ""테스트"", ""배포""]
|
||||||
|
}");
|
||||||
|
|
||||||
|
var mapper = new DataMapper(source, guide);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = mapper.Map();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsTrue(result.ContainsKey("Tags"));
|
||||||
|
var tags = result["Tags"] as JArray;
|
||||||
|
Assert.IsNotNull(tags);
|
||||||
|
Assert.AreEqual(3, tags.Count);
|
||||||
|
Assert.AreEqual("개발", tags[0].ToString());
|
||||||
|
Assert.AreEqual("테스트", tags[1].ToString());
|
||||||
|
Assert.AreEqual("배포", tags[2].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 복잡한 중첩 구조 매핑 테스트 - 새로 추가
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Map_ComplexNestedStructure_MapsCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var guide = new JObject
|
||||||
|
{
|
||||||
|
["Company"] = new JObject
|
||||||
|
{
|
||||||
|
["Name"] = "회사명",
|
||||||
|
["Founded"] = JToken.FromObject(DateTime.Now),
|
||||||
|
["Departments"] = new JArray
|
||||||
|
{
|
||||||
|
new JObject
|
||||||
|
{
|
||||||
|
["Name"] = "부서명",
|
||||||
|
["Employees"] = new JArray
|
||||||
|
{
|
||||||
|
new JObject
|
||||||
|
{
|
||||||
|
["Name"] = "직원명",
|
||||||
|
["Age"] = 30,
|
||||||
|
["Status"] = JToken.FromObject(UserStatus.Active)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var source = JObject.Parse(@"{
|
||||||
|
""Company"": {
|
||||||
|
""Name"": ""XYZ 주식회사"",
|
||||||
|
""Founded"": ""2000-01-01T00:00:00"",
|
||||||
|
""Departments"": [
|
||||||
|
{
|
||||||
|
""Name"": ""개발부"",
|
||||||
|
""Employees"": [
|
||||||
|
{ ""Name"": ""김개발"", ""Age"": 35, ""Status"": ""Active"" },
|
||||||
|
{ ""Name"": ""이테스트"", ""Age"": 28, ""Status"": ""Inactive"" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
""Name"": ""마케팅부"",
|
||||||
|
""Employees"": [
|
||||||
|
{ ""Name"": ""박마케팅"", ""Age"": 32, ""Status"": ""Active"" }
|
||||||
|
],
|
||||||
|
""Budget"": 500000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
""Address"": ""서울시 강남구""
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
var mapper = new DataMapper(source, guide);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = mapper.Map();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var company = result["Company"] as JObject;
|
||||||
|
Assert.IsNotNull(company);
|
||||||
|
Assert.AreEqual("XYZ 주식회사", company["Name"].ToString());
|
||||||
|
Assert.AreEqual(new DateTime(2000, 1, 1), company["Founded"].ToObject<DateTime>());
|
||||||
|
Assert.AreEqual("서울시 강남구", company["Address"].ToString());
|
||||||
|
|
||||||
|
var departments = company["Departments"] as JArray;
|
||||||
|
Assert.IsNotNull(departments);
|
||||||
|
Assert.AreEqual(2, departments.Count);
|
||||||
|
|
||||||
|
var devDept = departments[0] as JObject;
|
||||||
|
Assert.AreEqual("개발부", devDept["Name"].ToString());
|
||||||
|
var devEmployees = devDept["Employees"] as JArray;
|
||||||
|
Assert.AreEqual(2, devEmployees.Count);
|
||||||
|
Assert.AreEqual("김개발", devEmployees[0]["Name"].ToString());
|
||||||
|
Assert.AreEqual(35, devEmployees[0]["Age"].ToObject<int>());
|
||||||
|
Assert.AreEqual(UserStatus.Active, devEmployees[0]["Status"].ToObject<UserStatus>());
|
||||||
|
Assert.AreEqual("이테스트", devEmployees[1]["Name"].ToString());
|
||||||
|
Assert.AreEqual(UserStatus.Inactive, devEmployees[1]["Status"].ToObject<UserStatus>());
|
||||||
|
|
||||||
|
var marketingDept = departments[1] as JObject;
|
||||||
|
Assert.AreEqual("마케팅부", marketingDept["Name"].ToString());
|
||||||
|
Assert.AreEqual(500000, marketingDept["Budget"].ToObject<int>());
|
||||||
|
var marketingEmployees = marketingDept["Employees"] as JArray;
|
||||||
|
Assert.AreEqual(1, marketingEmployees.Count);
|
||||||
|
Assert.AreEqual("박마케팅", marketingEmployees[0]["Name"].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 다양한 형식의 배열 테스트 - 새로 추가
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Map_MixedArrayTypes_HandlesCorrectly()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var guide = new JObject
|
||||||
|
{
|
||||||
|
["MixedArray"] = new JArray
|
||||||
|
{
|
||||||
|
"문자열",
|
||||||
|
123,
|
||||||
|
true,
|
||||||
|
new JObject { ["Key"] = "Value" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var source = JObject.Parse(@"{
|
||||||
|
""MixedArray"": [
|
||||||
|
""테스트"",
|
||||||
|
456,
|
||||||
|
false,
|
||||||
|
{ ""Key"": ""NewValue"", ""Extra"": ""ExtraValue"" },
|
||||||
|
[1, 2, 3]
|
||||||
|
]
|
||||||
|
}");
|
||||||
|
|
||||||
|
var mapper = new DataMapper(source, guide);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = mapper.Map();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var mixedArray = result["MixedArray"] as JArray;
|
||||||
|
Assert.IsNotNull(mixedArray);
|
||||||
|
Assert.AreEqual(5, mixedArray.Count);
|
||||||
|
Assert.AreEqual("테스트", mixedArray[0].ToString());
|
||||||
|
Assert.AreEqual(456, mixedArray[1].ToObject<int>());
|
||||||
|
Assert.AreEqual(false, mixedArray[2].ToObject<bool>());
|
||||||
|
|
||||||
|
var objInArray = mixedArray[3] as JObject;
|
||||||
|
Assert.IsNotNull(objInArray);
|
||||||
|
Assert.AreEqual("NewValue", objInArray["Key"].ToString());
|
||||||
|
Assert.AreEqual("ExtraValue", objInArray["Extra"].ToString());
|
||||||
|
|
||||||
|
var nestedArray = mixedArray[4] as JArray;
|
||||||
|
Assert.IsNotNull(nestedArray);
|
||||||
|
Assert.AreEqual(3, nestedArray.Count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,49 +1,12 @@
|
|||||||
using System;
|
using UVC.Tests.Data;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
|
||||||
using UVC.Tests.Data;
|
|
||||||
|
|
||||||
namespace UVC.Tests
|
namespace UVC.Tests
|
||||||
{
|
{
|
||||||
public static class Tester
|
public static class Tester
|
||||||
{
|
{
|
||||||
|
|
||||||
public static void RunAllTests()
|
public static void RunAllTests()
|
||||||
{
|
{
|
||||||
Debug.Log("테스트 시작: DataMapperTests");
|
new DataMapperTests().TestAll();
|
||||||
RunDataMapperTests();
|
|
||||||
Debug.Log("테스트 완료: DataMapperTests");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RunDataMapperTests()
|
|
||||||
{
|
|
||||||
DataMapperTests test = new DataMapperTests();
|
|
||||||
|
|
||||||
RunTest("Map_StringProperty_MapsCorrectly", () => test.Map_StringProperty_MapsCorrectly());
|
|
||||||
RunTest("Map_IntProperty_MapsCorrectly", () => test.Map_IntProperty_MapsCorrectly());
|
|
||||||
RunTest("Map_DoubleProperty_MapsCorrectly", () => test.Map_DoubleProperty_MapsCorrectly());
|
|
||||||
RunTest("Map_BoolProperty_MapsCorrectly", () => test.Map_BoolProperty_MapsCorrectly());
|
|
||||||
RunTest("Map_DateTimeProperty_MapsCorrectly", () => test.Map_DateTimeProperty_MapsCorrectly());
|
|
||||||
RunTest("Map_EnumProperty_MapsCorrectly", () => test.Map_EnumProperty_MapsCorrectly());
|
|
||||||
RunTest("Map_AdditionalProperty_AddsToResult", () => test.Map_AdditionalProperty_AddsToResult());
|
|
||||||
RunTest("Map_InvalidDateTimeString_ReturnsNull", () => test.Map_InvalidDateTimeString_ReturnsNull());
|
|
||||||
RunTest("Map_ComplexObject_MapsAllProperties", () => test.Map_ComplexObject_MapsAllProperties());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RunTest(string testName, Action testMethod)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
testMethod();
|
|
||||||
Debug.Log($"테스트 성공: {testName}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.LogError($"테스트 실패: {testName} - {ex.Message}\n{ex.StackTrace}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user