#nullable enable /** * DpJsonProtocol — 레거시 호환 JSON 프로토콜. DpProtocolLibrary 모듈화. * Thrift/내부 전송용; 타입 래퍼(i64/str/tf/lst 등) 포함. 설정/OpenAPI 라운드트립은 Deuk JSON/YAML 사용. */ using System; using System.Collections.Generic; using System.IO; using System.Text; namespace DeukPack.Protocol { /// /// Legacy-compatible JSON protocol (TJSONProtocol-compatible). /// Uses type wrappers for wire/Thrift compatibility. For config/OpenAPI round-trip use Deuk JSON/YAML. /// Stream-based; constructor(stream, includeSchema, isReadMode). /// public class DpJsonProtocol : DpProtocol, IDisposable { private readonly Stream _stream; private readonly bool _isReadMode; private readonly bool _includeSchema; private readonly bool _pretty; private readonly Stack _writeStack; private class ListWriteState { public List List = new List(); public string FieldKey = ""; } private readonly Stack _listWriteStack; private readonly Stack _mapWriteStack; private string _currentFieldKey = ""; private DpWireType _currentFieldType; private string? _readMapCurrentKey = ""; // Initialized to empty to avoid null check skip private Dictionary _rootRead; private Stack _readStack; private KeyValuePair? _currentReadField; private List? _readList; private int _readListIndex; private Dictionary? _readMapDict; private List? _readMapKeys; private int _readMapIndex; private bool _readMapReadingKey; private readonly UTF8Encoding _utf8 = new UTF8Encoding(false); public DpJsonProtocol(Stream stream, bool pretty = false, bool includeSchema = false, bool isReadMode = true) { _stream = stream ?? throw new ArgumentNullException(nameof(stream)); _pretty = pretty; _includeSchema = includeSchema; _isReadMode = isReadMode; _contextTypes = new Stack(); _readContextTypes = new Stack(); _writeStack = new Stack(); _listWriteStack = new Stack(); _mapWriteStack = new Stack(); _readStack = new Stack(); if (isReadMode) { using (var sr = new StreamReader(stream, _utf8, false, 4096, true)) { var sb = new StringBuilder(); char[] buffer = new char[4096]; int read; while ((read = sr.Read(buffer, 0, buffer.Length)) > 0) { sb.Append(buffer, 0, read); if (sb.Length > 10 * 1024 * 1024) throw new Exception("Protocol buffer overflow: json exceeds max size 10MB"); } _rootRead = JsonProtocolParse(sb.ToString()); } } else { _rootRead = new Dictionary(); } } public void Dispose() { _stream?.Flush(); } private enum ContextType { Struct, List, Map } private readonly Stack _contextTypes; private struct JsonStructState { public Dictionary Obj; public bool IsMapKey; public string FieldKey; } private struct JsonReadFrame { public Dictionary Obj; public IEnumerator>? Enumerator; } private class MapWriteState { public readonly Dictionary Map = new Dictionary(); public object? PendingKey; public string FieldKey = ""; } private static string DpWireTypeToJsonKey(DpWireType t) { switch (t) { case DpWireType.Bool: return "tf"; case DpWireType.Byte: return "i8"; case DpWireType.Int16: return "i16"; case DpWireType.Int32: return "i32"; case DpWireType.Int64: return "i64"; case DpWireType.Double: return "dbl"; case DpWireType.String: return "str"; default: return DpTypeNames.ToProtocolName(t); } } private static object? WrapValueForJson(object? value) { if (value == null) return null; if (value is Dictionary d) return d; if (value is List l) return new Dictionary { { "lst", l } }; if (value is bool b) return new Dictionary { { "tf", b } }; if (value is int i) return new Dictionary { { "i32", (long)i } }; if (value is long v) return new Dictionary { { "i64", v.ToString() } }; if (value is short s) return new Dictionary { { "i16", (long)s } }; if (value is byte by) return new Dictionary { { "i8", (long)by } }; if (value is float || value is double f) return new Dictionary { { "dbl", Convert.ToDouble(value) } }; return new Dictionary { { "str", value.ToString() ?? "" } }; } private void WriteValueToCurrent(object value) { var ctx = _contextTypes.Count > 0 ? _contextTypes.Peek() : ContextType.Struct; if (ctx == ContextType.Map) { var top = _mapWriteStack.Peek(); if (top.PendingKey == null) top.PendingKey = value; else { var keyStr = top.PendingKey?.ToString() ?? ""; top.Map[keyStr] = WrapValueForJson(value); top.PendingKey = null; } return; } if (ctx == ContextType.List) { var wrappedChild = WrapValueForJson(value); if (wrappedChild != null) _listWriteStack.Peek().List.Add(wrappedChild); return; } var jsonVal = WrapValueForJson(value); if (jsonVal != null) _writeStack.Peek().Obj[_currentFieldKey] = jsonVal; } public void WriteStructBegin(DpRecord s) { _contextTypes.Push(ContextType.Struct); _writeStack.Push(new JsonStructState { Obj = new Dictionary(), IsMapKey = false, FieldKey = _currentFieldKey }); } public void WriteStructEnd() { var top = _writeStack.Pop(); _contextTypes.Pop(); _currentFieldKey = top.FieldKey; if (_contextTypes.Count > 0) { WriteValueToCurrent(top.Obj); } else { var json = JsonProtocolSerialize(top.Obj); if (_pretty) json = FormatJson(json); var bytes = _utf8.GetBytes(json); _stream.Write(bytes, 0, bytes.Length); _stream.Flush(); } } private static string FormatJson(string json) { var sb = new StringBuilder(); int indent = 0; bool quoted = false; for (int i = 0; i < json.Length; i++) { var ch = json[i]; if (ch == '"' && (i == 0 || json[i - 1] != '\\')) quoted = !quoted; if (quoted) { sb.Append(ch); continue; } if (ch == '{' || ch == '[') { sb.Append(ch).AppendLine().Append(new string(' ', ++indent * 2)); } else if (ch == '}' || ch == ']') { sb.AppendLine().Append(new string(' ', --indent * 2)).Append(ch); } else if (ch == ',') { sb.Append(ch).AppendLine().Append(new string(' ', indent * 2)); } else if (ch == ':') { sb.Append(ch).Append(" "); } else { sb.Append(ch); } } return sb.ToString(); } public void WriteFieldBegin(DpColumn f) { _currentFieldKey = f.ID.ToString(); _currentFieldType = f.Type; } public void WriteFieldEnd() { } public void WriteFieldStop() { } public void WriteBool(bool b) { WriteValueToCurrent(b); } public void WriteByte(byte b) { WriteValueToCurrent((int)(sbyte)b); } public void WriteI16(short v) { WriteValueToCurrent((int)v); } public void WriteI32(int v) { WriteValueToCurrent(v); } public void WriteI64(long v) { WriteValueToCurrent(v); } public void WriteDouble(double v) { WriteValueToCurrent(v); } public void WriteString(string? s) { WriteValueToCurrent(s ?? ""); } public void WriteBinary(byte[]? b) { WriteValueToCurrent(Convert.ToBase64String(b ?? Array.Empty())); } public void WriteListBegin(DpList list) { _contextTypes.Push(ContextType.List); _listWriteStack.Push(new ListWriteState { FieldKey = _currentFieldKey }); } public void WriteListEnd() { var state = _listWriteStack.Pop(); _contextTypes.Pop(); var wrapper = new Dictionary { { "lst", state.List } }; _currentFieldKey = state.FieldKey; WriteValueToCurrent(wrapper); } public void WriteSetBegin(DpSet set) { WriteListBegin(new DpList { ElementType = set.ElementType, Count = set.Count }); } public void WriteSetEnd() { WriteListEnd(); } public void WriteMapBegin(DpDict map) { _contextTypes.Push(ContextType.Map); _mapWriteStack.Push(new MapWriteState { FieldKey = _currentFieldKey }); } public void WriteMapEnd() { var state = _mapWriteStack.Pop(); _contextTypes.Pop(); var wrapper = new Dictionary { { "map", state.Map } }; _currentFieldKey = state.FieldKey; WriteValueToCurrent(wrapper); } private List? _pendingReadList; private Dictionary? _pendingReadMap; private struct ReadListState { public List List; public int Index; } private readonly Stack _readListStack = new Stack(); private struct ReadMapState { public Dictionary Dict; public List Keys; public string CurrentKey; public int Index; public bool ReadingKey; } private readonly Stack _readMapStack = new Stack(); private readonly Stack _readContextTypes; public DpRecord ReadStructBegin() { if (_readStack.Count == 0) _readStack.Push(new JsonReadFrame { Obj = _rootRead, Enumerator = _rootRead?.GetEnumerator() }); else { var ctx = _readContextTypes.Count > 0 ? _readContextTypes.Peek() : ContextType.Struct; if (ctx == ContextType.List) { var state = _readListStack.Pop(); if (state.Index < state.List.Count) { var nextObj = state.List[state.Index++] as Dictionary; if (nextObj != null) _readStack.Push(new JsonReadFrame { Obj = nextObj, Enumerator = nextObj.GetEnumerator() }); } _readListStack.Push(state); } else if (ctx == ContextType.Map) { var state = _readMapStack.Pop(); if (!state.ReadingKey && state.Index < state.Keys.Count) { if (state.Dict.TryGetValue(state.CurrentKey, out var mapVal) && mapVal is Dictionary mapStruct) { _readStack.Push(new JsonReadFrame { Obj = mapStruct, Enumerator = mapStruct.GetEnumerator() }); state.Index++; state.ReadingKey = true; } } _readMapStack.Push(state); } else if (_currentReadField.HasValue && _currentReadField.Value.Value is Dictionary nextObj) _readStack.Push(new JsonReadFrame { Obj = nextObj, Enumerator = nextObj.GetEnumerator() }); } _readContextTypes.Push(ContextType.Struct); return new DpRecord(""); } public void ReadStructEnd() { if (_readStack.Count > 0) _readStack.Pop(); if (_readContextTypes.Count > 0) _readContextTypes.Pop(); } public DpColumn ReadFieldBegin() { if (_readStack.Count == 0) return new DpColumn("", DpWireType.Stop, 0); var cur = _readStack.Peek(); if (cur.Enumerator == null || !cur.Enumerator.MoveNext()) return new DpColumn("", DpWireType.Stop, 0); var kv = cur.Enumerator.Current; var wrapper = kv.Value as Dictionary; if (wrapper == null) { _currentReadField = kv; return new DpColumn(kv.Key, DpWireType.String, 0); } DpWireType t = DpWireType.Stop; foreach (var key in wrapper.Keys) { t = DpTypeNames.FromProtocolName(key); break; } _currentReadField = kv; if ((t == DpWireType.List || t == DpWireType.Set) && wrapper.TryGetValue(DpTypeNames.ToProtocolName(t), out var listVal) && listVal is List list) { _pendingReadList = list; } else if (t == DpWireType.Map && wrapper.TryGetValue(DpTypeNames.ToProtocolName(t), out var mapVal) && mapVal is Dictionary mapDict) { _pendingReadMap = mapDict; } return new DpColumn(kv.Key, DpWireType.Void, short.TryParse(kv.Key, out var id) ? id : (short)0); } public void ReadFieldEnd() { } public bool ReadBool() { return ReadSingleValue("tf"); } public byte ReadByte() { return (byte)ReadI32(); } public short ReadI16() { return (short)ReadI32(); } public int ReadI32() { return (int)Convert.ToInt64(ReadSingleValue("i32")); } public long ReadI64() { var res = ReadSingleValue("i64"); if (res is string s) return long.Parse(s); return Convert.ToInt64(res); } public double ReadDouble() { return ReadSingleValue("dbl"); } public string? ReadString() { return ReadSingleValue("str") ?? ""; } public byte[]? ReadBinary() { var s = ReadSingleValue("str"); return string.IsNullOrEmpty(s) ? Array.Empty() : Convert.FromBase64String(s); } private T ReadSingleValue(string key) { object? v = null; var ctx = _readContextTypes.Count > 0 ? _readContextTypes.Peek() : ContextType.Struct; if (ctx == ContextType.Map) { var state = _readMapStack.Pop(); if (state.Index < state.Keys.Count) { if (state.ReadingKey) { v = state.CurrentKey = state.Keys[state.Index]; state.ReadingKey = false; } else { if (state.Dict.TryGetValue(state.CurrentKey, out var valObj)) { if (valObj is Dictionary dict) { if (dict.TryGetValue(key, out var tv1)) v = tv1; else if (dict.TryGetValue("i32", out tv1) || dict.TryGetValue("i64", out tv1) || dict.TryGetValue("i8", out tv1) || dict.TryGetValue("i16", out tv1)) v = tv1; else if (dict.TryGetValue("str", out tv1)) v = tv1; else if (dict.TryGetValue("dbl", out tv1)) v = tv1; else if (dict.TryGetValue("tf", out tv1)) v = tv1; } else v = valObj; } state.Index++; state.ReadingKey = true; } } _readMapStack.Push(state); } else if (ctx == ContextType.List) { var state = _readListStack.Pop(); if (state.Index < state.List.Count) { var raw = state.List[state.Index++]; if (raw is Dictionary dict) { if (dict.TryGetValue(key, out var tv2)) v = tv2; else if (dict.TryGetValue("i32", out tv2) || dict.TryGetValue("i64", out tv2) || dict.TryGetValue("i8", out tv2) || dict.TryGetValue("i16", out tv2)) v = tv2; else if (dict.TryGetValue("str", out tv2)) v = tv2; else if (dict.TryGetValue("dbl", out tv2)) v = tv2; else if (dict.TryGetValue("tf", out tv2)) v = tv2; } else v = raw; } _readListStack.Push(state); } else if (_currentReadField.HasValue) { var wrapper = _currentReadField.Value.Value as Dictionary; if (wrapper != null && wrapper.TryGetValue(key, out var tv3)) v = tv3; } if (v == null) return default!; if (v is T tvVal) return tvVal; if (typeof(T) == typeof(string)) return (T)(object)v.ToString()!; if (typeof(T) == typeof(bool)) return (T)(object)Convert.ToBoolean(v); if (typeof(T) == typeof(long)) return (T)(object)Convert.ToInt64(v); if (typeof(T) == typeof(double)) return (T)(object)Convert.ToDouble(v); if (typeof(T) == typeof(object)) return (T)v; return (T)Convert.ChangeType(v, typeof(T)); } public DpList ReadListBegin() { var lst = _pendingReadList ?? new List(); _pendingReadList = null; _readContextTypes.Push(ContextType.List); _readListStack.Push(new ReadListState { List = lst, Index = 0 }); return new DpList { ElementType = DpWireType.String, Count = lst.Count }; } public void ReadListEnd() { if (_readContextTypes.Count > 0) _readContextTypes.Pop(); if (_readListStack.Count > 0) _readListStack.Pop(); } public DpSet ReadSetBegin() { var l = ReadListBegin(); return new DpSet { ElementType = l.ElementType, Count = l.Count }; } public void ReadSetEnd() { ReadListEnd(); } public DpDict ReadMapBegin() { var md = _pendingReadMap ?? new Dictionary(); _pendingReadMap = null; _readContextTypes.Push(ContextType.Map); _readMapStack.Push(new ReadMapState { Dict = md, Keys = new List(md.Keys), CurrentKey = "", Index = 0, ReadingKey = true }); return new DpDict { KeyType = DpWireType.String, ValueType = DpWireType.String, Count = md.Count }; } public void ReadMapEnd() { if (_readContextTypes.Count > 0) _readContextTypes.Pop(); if (_readMapStack.Count > 0) _readMapStack.Pop(); } private static string JsonProtocolSerialize(Dictionary obj) { var sb = new StringBuilder(); sb.Append('{'); var first = true; foreach (var kv in obj) { if (!first) sb.Append(','); first = false; sb.Append('"').Append(EscapeJson(kv.Key)).Append("\":"); AppendJsonValue(sb, kv.Value); } sb.Append('}'); return sb.ToString(); } private static void AppendJsonValue(StringBuilder sb, object v) { if (v == null) { sb.Append("null"); return; } if (v is bool b) { sb.Append(b ? "true" : "false"); return; } if (v is int i) { sb.Append(i); return; } if (v is long l) { sb.Append(l); return; } if (v is double d) { sb.Append(d.ToString("R", System.Globalization.CultureInfo.InvariantCulture)); return; } if (v is string s) { sb.Append('"').Append(EscapeJson(s)).Append('"'); return; } if (v is Dictionary dict) { sb.Append(JsonProtocolSerialize(dict)); return; } if (v is List list) { sb.Append('['); for (int j = 0; j < list.Count; j++) { if (j > 0) sb.Append(','); AppendJsonValue(sb, list[j]); } sb.Append(']'); return; } sb.Append("null"); } private static string EscapeJson(string s) { if (string.IsNullOrEmpty(s)) return ""; var sb = new StringBuilder(); foreach (var c in s) { if (c == '"') sb.Append("\\\""); else if (c == '\\') sb.Append("\\\\"); else if (c == '\b') sb.Append("\\b"); else if (c == '\f') sb.Append("\\f"); else if (c == '\n') sb.Append("\\n"); else if (c == '\r') sb.Append("\\r"); else if (c == '\t') sb.Append("\\t"); else if (char.IsControl(c)) sb.AppendFormat("\\u{0:x4}", (int)c); else sb.Append(c); } return sb.ToString(); } private static Dictionary JsonProtocolParse(string json) { if (string.IsNullOrWhiteSpace(json)) return new Dictionary(); int i = 0; return ParseObject(json, ref i); } private static Dictionary ParseObject(string s, ref int i) { var obj = new Dictionary(); SkipWs(s, ref i); if (i >= s.Length || s[i] != '{') return obj; i++; while (i < s.Length) { SkipWs(s, ref i); if (i < s.Length && s[i] == '}') { i++; return obj; } var key = ParseString(s, ref i); SkipWs(s, ref i); if (i < s.Length && s[i] == ':') i++; SkipWs(s, ref i); var val = ParseValue(s, ref i); if (val != null) obj[key] = val; SkipWs(s, ref i); if (i < s.Length && s[i] == ',') i++; } return obj; } private static object? ParseValue(string s, ref int i) { SkipWs(s, ref i); if (i >= s.Length) return null; if (s[i] == '{') return ParseObject(s, ref i); if (s[i] == '[') return ParseArray(s, ref i); if (s[i] == '"') return ParseString(s, ref i); if (s[i] == 't' || s[i] == 'f') return ParseBool(s, ref i); if (s[i] == 'n') { ParseToken(s, ref i, "null"); return null; } return ParseNumber(s, ref i); } private static List ParseArray(string s, ref int i) { var list = new List(); if (i >= s.Length || s[i] != '[') return list; i++; SkipWs(s, ref i); while (i < s.Length && s[i] != ']') { list.Add(ParseValue(s, ref i)); SkipWs(s, ref i); if (i < s.Length && s[i] == ',') i++; } if (i < s.Length) i++; return list; } private static string ParseString(string s, ref int i) { if (i >= s.Length || s[i] != '"') return ""; i++; var sb = new StringBuilder(); while (i < s.Length && s[i] != '"') { if (s[i] == '\\') { i++; if (i < s.Length) { var ch = s[i++]; if (ch == 'n') sb.Append('\n'); else if (ch == 'r') sb.Append('\r'); else if (ch == 't') sb.Append('\t'); else if (ch == 'b') sb.Append('\b'); else if (ch == 'f') sb.Append('\f'); else if (ch == '"') sb.Append('"'); else if (ch == '\\') sb.Append('\\'); else if (ch == 'u' && i + 3 < s.Length) { var hex = s.Substring(i, 4); if (int.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out var code)) sb.Append((char)code); i += 4; } else sb.Append(ch); } } else sb.Append(s[i++]); } if (i < s.Length) i++; return sb.ToString(); } private static bool ParseBool(string s, ref int i) { if (ParseToken(s, ref i, "true")) return true; ParseToken(s, ref i, "false"); return false; } private static bool ParseToken(string s, ref int i, string tok) { int start = i; foreach (var c in tok) { if (i < s.Length && s[i] == c) i++; else { i = start; return false; } } return true; } private static object ParseNumber(string s, ref int i) { int start = i; if (i < s.Length && (s[i] == '-' || s[i] == '+')) i++; while (i < s.Length && char.IsDigit(s[i])) i++; if (i < s.Length && s[i] == '.') { i++; while (i < s.Length && char.IsDigit(s[i])) i++; return double.Parse(s.Substring(start, i - start), System.Globalization.CultureInfo.InvariantCulture); } return long.Parse(s.Substring(start, i - start), System.Globalization.CultureInfo.InvariantCulture); } private static void SkipWs(string s, ref int i) { while (i < s.Length && char.IsWhiteSpace(s[i])) i++; } } }