using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; namespace UVC.Extention { /// /// Dictionary 클래스에 대한 확장 메소드를 제공하는 클래스입니다. /// public static class DictionaryEx { /// /// Dictionary를 JSON 문자열로 변환합니다. /// 중첩된 Dictionary 객체도 재귀적으로 처리합니다. /// /// Dictionary의 키 타입 /// Dictionary의 값 타입 /// JSON 문자열로 변환할 Dictionary 객체 /// JSON 형식의 문자열 /// /// /// var dict = new Dictionary /// { /// { "name", "홍길동" }, /// { "age", 30 }, /// { "address", new Dictionary { { "city", "서울" }, { "zip", "12345" } } } /// }; /// /// string json = dict.ToJson(); /// // 결과: {"name":"홍길동","age":"30","address":{"city":"서울","zip":"12345"}} /// /// public static string ToJson(this IDictionary dict) { if (dict == null || dict.Count == 0) return "{}"; var sb = new StringBuilder("{"); foreach (var kvp in dict) { sb.Append($"\"{kvp.Key}\":"); // 값이 null인 경우 처리 if (kvp.Value == null) { sb.Append("null,"); continue; } // 값이 중첩된 Dictionary인 경우 재귀적으로 처리 if (kvp.Value is IDictionary dictObject) { string nestedJson = ToJson(dictObject); sb.Append($"{nestedJson},"); } else if (kvp.Value is IDictionary dictStringObject) { string nestedJson = ToJson(dictStringObject); sb.Append($"{nestedJson},"); } else if (kvp.Value is IDictionary dictStringString) { string nestedJson = ToJson(dictStringString); sb.Append($"{nestedJson},"); } // 값이 숫자인 경우 따옴표 없이 추가 else if (IsNumericType(kvp.Value.GetType())) { sb.Append($"{kvp.Value},"); } // 값이 불리언인 경우 따옴표 없이 추가 (소문자 true/false) else if (kvp.Value is bool boolValue) { sb.Append($"{boolValue.ToString().ToLowerInvariant()},"); } // 그 외의 경우 문자열로 처리 else { // 특수 문자를 이스케이프 처리 string escapedValue = EscapeJsonString(kvp.Value.ToString()); sb.Append($"\"{escapedValue}\","); } } if (dict.Count > 0) { sb.Length--; // 마지막 쉼표(,) 제거 } sb.Append("}"); return sb.ToString(); } /// /// 주어진 타입이 숫자형인지 확인합니다. /// /// 검사할 타입 /// 숫자형이면 true, 아니면 false private static bool IsNumericType(Type type) { if (type == null) return false; switch (Type.GetTypeCode(type)) { case TypeCode.Byte: case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; default: return false; } } /// /// JSON 문자열에서 특수 문자를 이스케이프 처리합니다. /// /// 이스케이프 처리할 문자열 /// 이스케이프 처리된 문자열 private static string EscapeJsonString(string value) { if (string.IsNullOrEmpty(value)) return value; StringBuilder sb = new StringBuilder(); foreach (char c in value) { switch (c) { case '\\': sb.Append("\\\\"); break; case '\"': sb.Append("\\\""); break; case '\n': sb.Append("\\n"); break; case '\r': sb.Append("\\r"); break; case '\t': sb.Append("\\t"); break; case '\b': sb.Append("\\b"); break; case '\f': sb.Append("\\f"); break; default: if (c < 32) // 제어 문자 { sb.Append($"\\u{(int)c:X4}"); } else { sb.Append(c); } break; } } return sb.ToString(); } /// /// 두 사전의 키와 값을 비교하여 두 사전이 같은지 확인합니다. /// /// 사전의 키 유형입니다. /// 사전의 값 유형입니다. /// 비교할 첫 번째 사전입니다. /// 비교할 두 번째 사전입니다. /// 의 모든 키가 에 존재하고 해당 값이 같으면 를 반환합니다. /// 그렇지 않으면 를 반환합니다. public static bool IsSame(this IDictionary dic, IDictionary other) { return !dic.Keys.Any(key => !object.Equals(dic[key], other[key])); } /// /// 두 사전을 비교하여 두 번째 사전의 키와 값을 포함하는 새 사전을 반환합니다. /// 두 사전의 값이 첫 번째 사전의 값과 다릅니다. /// /// 이 메서드는 를 사용하여 얕은 값 비교를 수행합니다. 키가 에는 있지만 에는 없는 경우, 또는 그 반대의 경우, 해당 키는 무시됩니다. /// 사전에 있는 키의 타입입니다. /// 사전에 있는 값의 타입입니다. /// 비교할 원본 사전입니다. /// 비교할 업데이트된 사전입니다. /// 의 키가 포함된 사전입니다. 해당 키의 값이 의 해당 값과 다른 경우입니다. /// 반환된 사전의 값은 에서 가져옵니다. public static IDictionary Differences(this IDictionary oldDict, IDictionary newDict) { var result = new Dictionary(); foreach (var key in oldDict.Keys) { TValue oldValue = oldDict[key]; TValue newValue = newDict[key]; if (!object.Equals(oldValue, newValue)) { result.Add(key, newValue); } } return result; } /// /// 두 사전을 비교하여 두 번째 사전의 키와 값을 포함하는 새 사전을 반환합니다. /// 두 사전의 값이 첫 번째 사전의 값과 다릅니다. /// /// 이 메서드는 심층 비교를 사용하여 값이 다른지 확인합니다. 비교 /// 로직은 DeepEquals 메서드로 정의됩니다. 에는 있지만 /// 에는 없는 키는 결과에 포함되지 않습니다. /// 사전에 있는 키의 타입입니다. /// 사전에 있는 값의 타입입니다. /// 비교할 첫 번째 사전입니다. /// 비교할 두 번째 사전입니다. /// 의 키와 값을 포함하는 사전으로, 값이 의 값과 다른 경우 /// 에 키가 있지만 값이 같지 않으면 /// 의 키와 해당 값이 포함됩니다. public static IDictionary DeepDifferences(this IDictionary dict1, IDictionary dict2) { var result = new Dictionary(); foreach (var key in dict1.Keys) { TValue val1 = dict1[key]; TValue val2 = dict2[key]; if (!DeepEquals(val1, val2)) { result.Add(key, val2); } } return result; } /// /// 두 객체의 값, 속성 및 중첩 구조를 비교하여 두 객체가 깊이 동일한지 여부를 확인합니다. /// // /// 이 메서드는 제공된 객체와 중첩 구조를 재귀적으로 비교합니다. /// 기본 유형, 문자열, 소수, 컬렉션(예: 배열,리스트), 사전 및 복합 객체의 비교를 지원합니다. /// 컬렉션의 경우, 이 메서드는 요소를 순서대로 비교합니다. /// 사전의 경우, 키와 연관된 값을 비교합니다. 객체의 유형이 다르거나 일치하지 않는 구조를 가진 경우, /// 이 메서드는 를 반환합니다. /// 비교할 첫 번째 객체입니다. null일 수 있습니다. /// 비교할 두 번째 객체입니다. null일 수 있습니다. /// 객체가 깊이 동일하면 를 반환하고, 그렇지 않으면 를 반환합니다. 깊이 동일성 ///에는 기본 값, 문자열, 컬렉션, 사전 및 복합 객체의 공개 속성 비교가 포함됩니다. /// private static bool DeepEquals(object obj1, object obj2) { if (ReferenceEquals(obj1, obj2)) return true; if (obj1 == null || obj2 == null) return false; if (obj1.Equals(obj2)) return true; var type1 = obj1.GetType(); var type2 = obj2.GetType(); if (type1 != type2) return false; // String or primitive if (type1.IsPrimitive || obj1 is string || obj1 is decimal) return obj1.Equals(obj2); // List or Array if (obj1 is IEnumerable enumerable1 && obj2 is IEnumerable enumerable2) { var list1 = enumerable1.Cast().ToList(); var list2 = enumerable2.Cast().ToList(); if (list1.Count != list2.Count) return false; for (int i = 0; i < list1.Count; i++) { if (!DeepEquals(list1[i], list2[i])) return false; } return true; } // Dictionary if (obj1 is IDictionary dict1 && obj2 is IDictionary dict2) { if (dict1.Count != dict2.Count) return false; foreach (var key in dict1.Keys) { if (!dict2.Contains(key)) return false; if (!DeepEquals(dict1[key], dict2[key])) return false; } return true; } // Complex object var props = type1.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in props) { var val1 = prop.GetValue(obj1); var val2 = prop.GetValue(obj2); if (!DeepEquals(val1, val2)) return false; } return true; } } }