Files
XRLib/Assets/Scripts/UVC/Extention/DictionaryEx.cs
2025-06-04 23:10:11 +09:00

158 lines
5.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
namespace UVC.Extention
{
/// <summary>
/// Dictionary 클래스에 대한 확장 메소드를 제공하는 클래스입니다.
/// </summary>
public static class DictionaryEx
{
/// <summary>
/// Dictionary를 JSON 문자열로 변환합니다.
/// 중첩된 Dictionary 객체도 재귀적으로 처리합니다.
/// </summary>
/// <typeparam name="TKey">Dictionary의 키 타입</typeparam>
/// <typeparam name="TValue">Dictionary의 값 타입</typeparam>
/// <param name="dict">JSON 문자열로 변환할 Dictionary 객체</param>
/// <returns>JSON 형식의 문자열</returns>
/// <example>
/// <code>
/// var dict = new Dictionary<string, object>
/// {
/// { "name", "홍길동" },
/// { "age", 30 },
/// { "address", new Dictionary<string, string> { { "city", "서울" }, { "zip", "12345" } } }
/// };
///
/// string json = dict.ToJson();
/// // 결과: {"name":"홍길동","age":"30","address":{"city":"서울","zip":"12345"}}
/// </code>
/// </example>
public static string ToJson<TKey, TValue>(this IDictionary<TKey, TValue> 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<object, object> dictObject)
{
string nestedJson = ToJson(dictObject);
sb.Append($"{nestedJson},");
}
else if (kvp.Value is IDictionary<string, object> dictStringObject)
{
string nestedJson = ToJson(dictStringObject);
sb.Append($"{nestedJson},");
}
else if (kvp.Value is IDictionary<string, string> 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();
}
/// <summary>
/// 주어진 타입이 숫자형인지 확인합니다.
/// </summary>
/// <param name="type">검사할 타입</param>
/// <returns>숫자형이면 true, 아니면 false</returns>
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;
}
}
/// <summary>
/// JSON 문자열에서 특수 문자를 이스케이프 처리합니다.
/// </summary>
/// <param name="value">이스케이프 처리할 문자열</param>
/// <returns>이스케이프 처리된 문자열</returns>
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();
}
}
}