using Newtonsoft.Json.Linq; using System; namespace UVC.Data { /// /// 서로 다른 JSON 데이터 구조 간에 매핑 기능을 제공하는 클래스입니다. /// /// /// 이 클래스는 JSON 데이터를 원하는 형식으로 변환하거나, 중첩된 구조(nested structure)도 처리할 수 있습니다. /// 소스 JSON 객체의 속성을 가이드 객체에 정의된 타입에 따라 적절히 변환합니다. /// /// /// 기본 사용 예시: /// /// // 소스 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는 원본과 동일한 구조이며 각 속성이 가이드에 따라 타입 변환됨 /// /// public class DataMapper { private JObject source; private JObject guide; /// /// DataMapper 클래스의 새 인스턴스를 초기화합니다. /// /// 매핑할 원본 JSON 객체 /// 타입 변환을 위한 가이드 JSON 객체 /// /// 가이드 객체는 원본 JSON 객체와 동일한 구조를 가질 필요는 없지만, /// 변환하려는 속성들에 대한 타입 정보를 제공해야 합니다. /// public DataMapper(JObject source, JObject target) { this.source = source; this.guide = target; } /// /// 소스 객체를 가이드 객체를 기반으로 매핑하여 새로운 JSON 객체를 생성합니다. /// /// 매핑된 JSON 객체 /// /// /// var mapper = new DataMapper(sourceJson, guideJson); /// JObject result = mapper.Map(); /// Debug.Log(result["name"].ToString()); // "김철수" /// Debug.Log(result["age"].ToObject<int>()); // 30 /// /// public JObject Map() { return MapObject(source, guide); } /// /// 객체를 재귀적으로 매핑합니다. /// /// 원본 JSON 객체 /// 가이드 JSON 객체 /// 매핑된 JSON 객체 /// /// 이 메서드는 중첩된 객체와 배열을 포함하여 JSON 구조를 재귀적으로 처리합니다. /// /// /// 중첩 객체 매핑 예시: /// /// 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과 동일한 중첩 구조를 유지 /// /// private JObject MapObject(JObject sourceObject, JObject guideObject) { JObject target = new JObject(); foreach (var property in sourceObject.Properties()) { if (guideObject.ContainsKey(property.Name)) { JToken guideValue = guideObject[property.Name]; JToken sourceValue = property.Value; // 중첩된 객체 처리 if (sourceValue.Type == JTokenType.Object && guideValue.Type == JTokenType.Object) { target[property.Name] = MapObject((JObject)sourceValue, (JObject)guideValue); } // 중첩된 배열 처리 else if (sourceValue.Type == JTokenType.Array && guideValue.Type == JTokenType.Array) { target[property.Name] = MapArray((JArray)sourceValue, (JArray)guideValue); } else { MapProperty(property.Name, sourceValue, guideValue, target); } } else { target[property.Name] = property.Value; } } return target; } /// /// 배열을 재귀적으로 매핑합니다. /// /// 원본 JSON 배열 /// 가이드 JSON 배열 /// 매핑된 JSON 배열 /// /// 가이드 배열이 비어있으면, 원본 배열을 그대로 복사합니다. /// 그렇지 않으면, 가이드 배열의 첫 번째 항목을 템플릿으로 사용하여 원본 배열의 각 항목을 매핑합니다. /// /// /// 배열 매핑 예시: /// /// 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는 원본 배열과 동일한 구조의 배열 /// /// 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; } /// /// 개별 속성을 매핑합니다. /// /// 속성 이름 /// 매핑할 원본 값 /// 타입을 결정하는 가이드 값 /// 값을 추가할 대상 객체 /// /// 이 메서드는 가이드 값의 타입에 따라 원본 값을 적절한 타입으로 변환합니다. /// 지원되는 타입: 문자열, 정수, 실수, 불리언, 날짜/시간, 열거형, DataValueMapper /// /// /// 다양한 타입 매핑 예시: /// /// 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에는 모든 속성이 적절한 타입으로 변환됨 /// /// private void MapProperty(string propertyName, JToken sourceValue, JToken guideValue, JObject target) { if (guideValue.Type == JTokenType.String && sourceValue.Type == JTokenType.String) { target[propertyName] = sourceValue.ToObject(); } else if (guideValue.Type == JTokenType.Integer && sourceValue.Type == JTokenType.Integer) { target[propertyName] = sourceValue.ToObject(); } else if (guideValue.Type == JTokenType.Float && sourceValue.Type == JTokenType.Float) { target[propertyName] = sourceValue.ToObject(); } else if (guideValue.Type == JTokenType.Boolean && sourceValue.Type == JTokenType.Boolean) { target[propertyName] = sourceValue.ToObject(); } else if (guideValue.Type == JTokenType.Date && sourceValue.Type == JTokenType.String) { string dateStr = sourceValue.ToObject(); if (DateTime.TryParse(dateStr, out DateTime dateValue)) { target[propertyName] = JToken.FromObject(dateValue); } else { target[propertyName] = null; } } else if (guideValue.ToObject()?.GetType()?.IsEnum == true && sourceValue.Type == JTokenType.String) { Type enumType = guideValue.ToObject().GetType(); target[propertyName] = JToken.FromObject(Enum.Parse(enumType, sourceValue.ToObject(), true)); } else if (guideValue.Type == JTokenType.Object && sourceValue.Type == JTokenType.String) { try { // 먼저 DataValueMapper로 변환 시도 var dataValueMapper = guideValue.ToObject(); if (dataValueMapper != null) { string strValue = sourceValue.ToObject(); 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; } } } }