data pipeline 개발

This commit is contained in:
logonkhi
2025-06-05 20:09:28 +09:00
parent ef11fd4a14
commit 4db2791486
108 changed files with 62831 additions and 294 deletions

View File

@@ -0,0 +1,318 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace UVC.Data
{
/// <summary>
/// DataObject 객체 컬렉션의 변경사항을 추적하는 데이터 배열 클래스
/// </summary>
public class DataArray : List<DataObject>, IDataObject
{
// 추가 된 항목 목록
protected List<DataObject> addedList = new List<DataObject>();
// 제거 된 항목 목록
protected List<DataObject> removedList = new List<DataObject>();
// 수정 된 항목 목록
protected List<DataObject> modifiedList = new List<DataObject>();
// 추가된 항목에 접근할 수 있는 읽기 전용 컬렉션
public ReadOnlyCollection<DataObject> AddedItems => addedList.AsReadOnly();
// 제거된 항목에 접근할 수 있는 읽기 전용 컬렉션
public ReadOnlyCollection<DataObject> RemovedItems => removedList.AsReadOnly();
// 제거된 항목에 접근할 수 있는 읽기 전용 컬렉션
public ReadOnlyCollection<DataObject> ModifiedList => modifiedList.AsReadOnly();
/// <summary>
/// 변경 추적 활성화 여부
/// </summary>
public bool IsChangeTracking { get; set; } = false;
/// <summary>
/// 기본 생성자
/// </summary>
public DataArray() : base()
{
}
/// <summary>
/// 초기 용량을 지정하는 생성자
/// </summary>
/// <param name="capacity">초기 용량</param>
public DataArray(int capacity) : base(capacity)
{
}
/// <summary>
/// 기존 컬렉션으로부터 생성하는 생성자
/// </summary>
/// <param name="collection">초기 항목을 포함하는 컬렉션</param>
public DataArray(IEnumerable<DataObject> collection) : base(collection)
{
}
public DataArray(string jsonString) : base()
{
if (!string.IsNullOrEmpty(jsonString))
{
try
{
JArray jArray = JArray.Parse(jsonString);
foreach (var item in jArray)
{
Add(ConvertToDataObject(item));
}
}
catch (Exception ex)
{
throw new ArgumentException("Invalid JSON string format.", nameof(jsonString), ex);
}
}
}
/// <summary>
/// JArray로부터 DataArray를 생성하는 생성자
/// </summary>
/// <param name="jArray">JSON 배열</param>
public DataArray(JArray jArray) : base()
{
if (jArray != null)
{
foreach (var item in jArray)
{
Add(ConvertToDataObject(item));
}
}
}
/// <summary>
/// JToken을 DataObject로 변환합니다.
/// </summary>
private DataObject ConvertToDataObject(JToken token)
{
if (token.Type == JTokenType.Object)
{
return new DataObject((JObject)token);
}
else
{
// JObject가 아닌 경우, 새 DataObject를 만들고 값을 넣어줍니다
var dataObject = new DataObject();
dataObject.Add("value", ConvertJTokenToObject(token));
return dataObject;
}
}
private object ConvertJTokenToObject(JToken token)
{
switch (token.Type)
{
case JTokenType.String:
return token.ToString();
case JTokenType.Integer:
return token.ToObject<int>();
case JTokenType.Float:
return token.ToObject<float>();
case JTokenType.Boolean:
return token.ToObject<bool>();
case JTokenType.Object:
return new DataObject((JObject)token);
case JTokenType.Array:
JArray array = (JArray)token;
return new DataArray(array);
default:
return token.ToString();
}
}
/// <summary>
/// 항목을 추가합니다.
/// </summary>
/// <param name="item">추가할 항목</param>
public new void Add(DataObject item)
{
base.Add(item);
// 변경 사항을 추적하고 이벤트 발생
if (IsChangeTracking)
{
int index = Count - 1;
addedList.Add(item);
}
}
/// <summary>
/// 항목을 제거합니다.
/// </summary>
/// <param name="item">제거할 항목</param>
/// <returns>제거 성공 여부</returns>
public new bool Remove(DataObject item)
{
int index = IndexOf(item);
if (index >= 0)
{
RemoveAt(index);
return true;
}
return false;
}
/// <summary>
/// 지정된 인덱스의 항목을 제거합니다.
/// </summary>
/// <param name="index">제거할 항목의 인덱스</param>
public new void RemoveAt(int index)
{
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
DataObject removedItem = this[index];
base.RemoveAt(index);
if (IsChangeTracking)
{
removedList.Add(removedItem);
}
}
/// <summary>
/// 컬렉션의 모든 항목을 제거합니다.
/// </summary>
public new void Clear()
{
if (Count > 0)
{
var oldItems = new List<DataObject>(this);
base.Clear();
if (IsChangeTracking)
{
// 모든 항목을 제거 목록에 추가
removedList.AddRange(oldItems);
}
}
}
/// <summary>
/// 지정된 인덱스에 항목을 삽입합니다.
/// </summary>
/// <param name="index">삽입할 위치</param>
/// <param name="item">삽입할 항목</param>
public new void Insert(int index, DataObject item)
{
base.Insert(index, item);
if (IsChangeTracking)
{
addedList.Add(item);
}
}
/// <summary>
/// 지정된 인덱스의 항목을 교체합니다.
/// </summary>
/// <param name="index">교체할 위치</param>
/// <param name="item">새 항목</param>
public new DataObject this[int index]
{
get { return base[index]; }
set
{
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
DataObject oldItem = base[index];
base[index] = value;
if (IsChangeTracking)
{
addedList.Add(value);
removedList.Add(oldItem);
}
}
}
/// <summary>
/// 범위의 항목을 추가합니다.
/// </summary>
/// <param name="collection">추가할 항목의 컬렉션</param>
public new void AddRange(IEnumerable<DataObject> collection)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
int startIndex = Count;
base.AddRange(collection);
if (IsChangeTracking)
{
var items = collection.ToList();
for (int i = 0; i < items.Count; i++)
{
addedList.Add(items[i]);
}
}
}
/// <summary>
/// 변경된 인덱스 목록을 초기화합니다.
/// </summary>
public void ResetChangedIndices()
{
addedList.Clear();
removedList.Clear();
modifiedList.Clear();
}
/// <summary>
/// 변경 내역을 저장하지 않고 항목을 추가합니다.
/// </summary>
/// <param name="item">추가할 항목</param>
public void AddWithoutTracking(DataObject item)
{
bool oldTracking = IsChangeTracking;
IsChangeTracking = false;
Add(item);
IsChangeTracking = oldTracking;
}
/// <summary>
/// DataArray를 JArray로 변환합니다.
/// </summary>
public JArray ToJArray()
{
JArray array = new JArray();
foreach (var item in this)
{
array.Add(item);
}
return array;
}
/// <summary>
/// DataArray를 JArray로 암시적 변환합니다.
/// </summary>
public static implicit operator JArray(DataArray dataArray)
{
return dataArray.ToJArray();
}
/// <summary>
/// JArray를 DataArray로 암시적 변환합니다.
/// </summary>
public static implicit operator DataArray(JArray jArray)
{
return new DataArray(jArray);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5ea1772f89851a04fa8a65b43169561b

View File

@@ -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&lt;int&gt;()); // 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);
}
}
}
}

View File

@@ -0,0 +1,309 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace UVC.Data
{
public class DataObject : Dictionary<string, object>, IDataObject
{
// 직접적인 변경이 있었던 키를 저장하는 리스트
protected List<string> changedProperies = new List<string>();
public ReadOnlyCollection<string> ChangedProperies => changedProperies.AsReadOnly();
// 기존에 있던 속성 키를 추적하기 위한 집합
private HashSet<string> existingProperties = new HashSet<string>();
public DataObject()
{
}
public DataObject(JObject other)
{
// JObject로부터 속성을 복사
foreach (var prop in other.Properties())
{
// JToken을 object로 변환
this[prop.Name] = ConvertJTokenToObject(prop.Value);
existingProperties.Add(prop.Name);
}
}
// Dictionary로 초기화하는 생성자 추가
public DataObject(Dictionary<string, object> dictionary) : base(dictionary)
{
// 생성자에서 초기 속성들을 기존 속성으로 등록
foreach (var key in dictionary.Keys)
{
existingProperties.Add(key);
}
}
// JToken을 실제 객체로 변환하는 헬퍼 메서드
private object ConvertJTokenToObject(JToken token)
{
switch (token.Type)
{
case JTokenType.String:
return token.ToString();
case JTokenType.Integer:
return token.ToObject<int>();
case JTokenType.Float:
return token.ToObject<float>();
case JTokenType.Boolean:
return token.ToObject<bool>();
case JTokenType.Object:
return new DataObject((JObject)token);
case JTokenType.Array:
JArray array = (JArray)token;
return new DataArray(array);
default:
return token.ToString();
}
}
/// <summary>
/// 모든 프로퍼티를 변경된 것으로 표시합니다.
/// </summary>
public void ChangeAll()
{
changedProperies.Clear();
foreach (var key in this.Keys)
{
changedProperies.Add(key);
}
}
/// <summary>
/// 변경된 키 목록을 초기화합니다.
/// </summary>
public void ClearChangedKeys()
{
changedProperies.Clear();
}
/// <summary>
/// 속성이 변경될 때 호출되는 메서드입니다.
/// </summary>
protected virtual void OnPropertyChanged(string propertyName)
{
// 기존에 존재하던 속성인 경우에만 변경된 것으로 추적
if (existingProperties.Contains(propertyName))
{
if (!changedProperies.Contains(propertyName))
{
changedProperies.Add(propertyName);
}
}
else
{
// 새로 추가된 속성은 기존 속성 목록에 추가
existingProperties.Add(propertyName);
}
}
/// <summary>
/// 새 속성을 추가할 때 이벤트 연결을 처리합니다.
/// </summary>
public new void Add(string propertyName, object value)
{
// 추가하기 전에 확인 - 속성이 이미 있는지 확인
bool isExisting = ContainsKey(propertyName);
// 기본 구현 호출
base.Add(propertyName, value);
// 새로 추가된 속성이면 기존 속성 목록에 추가
if (!isExisting)
{
existingProperties.Add(propertyName);
}
}
/// <summary>
/// 인덱서를 통한 속성 설정을 처리합니다.
/// </summary>
public new object this[string key]
{
get => base[key];
set
{
// 속성 설정 전에 기존에 있는 속성인지 확인
bool isExisting = ContainsKey(key) || existingProperties.Contains(key);
// 기본 구현 호출
base[key] = value;
// 기존 속성이었다면 변경된 것으로 처리
if (isExisting)
{
if (!changedProperies.Contains(key))
{
changedProperies.Add(key);
}
}
else
{
// 새로 추가된 속성은 기존 속성으로 등록
existingProperties.Add(key);
}
OnPropertyChanged(key);
}
}
public int GetInt(string propertyName, int defaultValue = 0)
{
if (TryGetValue(propertyName, out object value) && value != null)
{
if (value is int intValue)
return intValue;
return Convert.ToInt32(value);
}
return defaultValue;
}
public string GetString(string propertyName, string defaultValue = "")
{
if (TryGetValue(propertyName, out object value) && value != null)
{
return value.ToString();
}
return defaultValue;
}
public bool GetBool(string propertyName, bool defaultValue = false)
{
if (TryGetValue(propertyName, out object value) && value != null)
{
if (value is bool boolValue)
return boolValue;
return Convert.ToBoolean(value);
}
return defaultValue;
}
public float GetFloat(string propertyName, float defaultValue = 0f)
{
if (TryGetValue(propertyName, out object value) && value != null)
{
if (value is float floatValue)
return floatValue;
return Convert.ToSingle(value);
}
return defaultValue;
}
public double GetDouble(string propertyName, double defaultValue = 0.0)
{
if (TryGetValue(propertyName, out object value) && value != null)
{
if (value is double doubleValue)
return doubleValue;
return Convert.ToDouble(value);
}
return defaultValue;
}
public DateTime? GetDateTime(string propertyName, DateTime? defaultValue = null)
{
if (TryGetValue(propertyName, out object value) && value != null)
{
if (value is DateTime dateTime)
return dateTime;
return Convert.ToDateTime(value);
}
return defaultValue;
}
public T GetEnum<T>(string propertyName, T defaultValue = default) where T : Enum
{
if (TryGetValue(propertyName, out object value) && value != null)
{
if (value is T enumValue)
return enumValue;
return (T)Enum.Parse(typeof(T), value.ToString());
}
return defaultValue;
}
public DataArray GetDataArray(string propertyName, DataArray? defaultValue = null)
{
if (TryGetValue(propertyName, out object value) && value != null)
{
if (value is DataArray dataArray)
return dataArray;
if (value is JArray jArray)
return new DataArray(jArray);
}
return defaultValue;
}
public DataObject GetDataObject(string propertyName, DataObject? defaultValue = null)
{
if (TryGetValue(propertyName, out object value) && value != null)
{
if (value is DataObject dataObject)
return dataObject;
if (value is JObject jObject)
return new DataObject(jObject);
if (value is Dictionary<string, object> dict)
return new DataObject(dict);
}
return defaultValue;
}
/// <summary>
/// 속성이 제거될 때 기존 속성 목록에서도 제거합니다.
/// </summary>
public new bool Remove(string key)
{
bool result = base.Remove(key);
if (result)
{
existingProperties.Remove(key);
changedProperies.Remove(key);
}
return result;
}
/// <summary>
/// 모든 속성을 제거할 때 기존 속성 목록과 변경된 키 목록도 초기화합니다.
/// </summary>
public void RemoveAll()
{
base.Clear();
existingProperties.Clear();
changedProperies.Clear();
}
/// <summary>
/// JObject로 변환
/// </summary>
public JObject ToJObject()
{
JObject result = new JObject();
foreach (var kvp in this)
{
result[kvp.Key] = JToken.FromObject(kvp.Value);
}
return result;
}
public void SetDifferent(DataObject other)
{
changedProperies.Clear();
foreach (var keyValue in other)
{
if(!this.ContainsKey(keyValue.Key) || !this[keyValue.Key].Equals(keyValue.Value))
{
this[keyValue.Key] = keyValue.Value;
changedProperies.Add(keyValue.Key);
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ca43f1365775ee443b6290a4079de628

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
namespace UVC.Data
{
public class DataRepository
{
#region Singleton
protected static readonly Lazy<DataRepository> instance = new Lazy<DataRepository>(() => new DataRepository());
protected DataRepository() { }
public static DataRepository Instance { get { return instance.Value; } }
#endregion
private Dictionary<string, DataObject> dataObjects = new Dictionary<string, DataObject>();
public void AddData(string key, DataObject dataObject)
{
if (!dataObjects.ContainsKey(key))
{
dataObject.ChangeAll();
dataObjects.Add(key, dataObject);
}
else
{
DataObject obj = dataObjects[key];
obj.SetDifferent(obj);
}
}
public void RemoveData(string key)
{
if (dataObjects.ContainsKey(key))
{
dataObjects.Remove(key);
}
}
public DataObject GetData(string key)
{
if (dataObjects.ContainsKey(key))
{
return dataObjects[key];
}
return null;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8654f538ded07d44385d806ada0cf1b5

View File

@@ -1,13 +0,0 @@
using System.Collections.Generic;
namespace UVC.Data
{
public class DataRequestInfo
{
private string url;
private string method;
private Dictionary<string, string> headers;
private string body;
}
}

View File

@@ -0,0 +1,54 @@
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using UVC.Network;
namespace UVC.Data
{
public class HttpPipeLine
{
private Dictionary<string, HttpPipeLineInfo> infoList = new Dictionary<string, HttpPipeLineInfo>();
public void Add(string key, HttpPipeLineInfo info)
{
if (!infoList.ContainsKey(key))
{
infoList.Add(key, info);
}
else
{
infoList[key] = info; // Update existing entry
}
}
public void Remove(string key)
{
if (infoList.ContainsKey(key))
{
infoList.Remove(key);
}
}
public async void Excute(string key)
{
if (infoList.ContainsKey(key))
{
HttpPipeLineInfo info = infoList[key];
string result = await HttpRequester.Request<string>(info.url, info.method, info.body, info.headers);
JObject jsonResult = JObject.Parse(result);
DataObject dataObject = new DataObject(jsonResult);
if (info.dataMapper != null)
{
dataObject = info.dataMapper.Map(jsonResult);
}
DataRepository.Instance.AddData(key, dataObject);
if (info.handler != null)
{
info.handler(dataObject);
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f0780f29b8d71ea46938f4804b5b7ca2

View File

@@ -0,0 +1,50 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
namespace UVC.Data
{
public class HttpPipeLineInfo
{
public string url;
public string method;
public Dictionary<string, string> headers;
public string body;
public Action<IDataObject> handler = null;
public bool repeat = false; // 반복 실행 여부
public int repeatCount = 0; // 반복 횟수, 0은 무한 반복
public int repeatInterval = 1000; // 반복 간격 (ms)
public DataMapper dataMapper = null; // 데이터 매퍼
public HttpPipeLineInfo(string url, string method = "post", Dictionary<string, string> headers = null, string body = null)
{
this.url = url;
this.method = method;
this.headers = headers;
this.body = body;
}
public HttpPipeLineInfo setDataMapper(DataMapper dataMapper)
{
this.dataMapper = dataMapper;
return this;
}
public HttpPipeLineInfo setHandler(Action<IDataObject> handler)
{
this.handler = handler;
return this;
}
public HttpPipeLineInfo setRepeat(bool repeat, int count = 0, int interval = 1000)
{
this.repeat = repeat;
this.repeatCount = count;
this.repeatInterval = interval;
return this;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 15e27eb73804cd548972be4bdd89a058

View File

@@ -0,0 +1,6 @@
namespace UVC.Data
{
public interface IDataObject
{
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 73168fc72a50dab44adc23095e0341be

View File

@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace UVC.Data
{
public class MQTTPipeLine
{
}
}

View File

@@ -0,0 +1,29 @@
using System;
namespace UVC.Data
{
public class MQTTPipeLineInfo
{
public string topic; // MQTT 토픽
public Action<DataObject> handler = null; // 메시지 핸들러
public DataMapper dataMapper = null; // 데이터 매퍼
public MQTTPipeLineInfo(string topic)
{
this.topic = topic;
}
public MQTTPipeLineInfo setHandler(Action<DataObject> handler)
{
this.handler = handler;
return this;
}
public MQTTPipeLineInfo setDataMapper(DataMapper dataMapper)
{
this.dataMapper = dataMapper;
return this;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d808f1610804b40429477dd3193c93e5