using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Superlazy.Loader { public static class JsonLoader { private static void ParseElement(SLEntity context, string token, string tokenName, bool quoted) { if (context.HasChild(tokenName)) { throw new Exception($"{tokenName} Already has child"); } if (quoted) { context[tokenName] = token; return; } { if (double.TryParse(token, out var val)) { if (val < 1 || val > int.MaxValue || (token.IsRight(".0") == false && token.Contains('.'))) { context[tokenName] = val; } else { context[tokenName] = (int)val; } } else { context[tokenName] = token; } } } public static SLEntity Parse(SLEntity root, string aJSON) { var stack = new Stack(); SLEntity context = null; var i = 0; var Token = new StringBuilder(); var TokenName = ""; var QuoteMode = false; var TokenIsQuoted = false; while (i < aJSON.Length) { switch (aJSON[i]) { case '{': if (QuoteMode) { Token.Append(aJSON[i]); break; } if (context is null) { stack.Push(root); } else { stack.Push(context[TokenName]); } TokenName = ""; Token.Length = 0; context = stack.Peek(); break; case '[': if (QuoteMode) { Token.Append(aJSON[i]); break; } stack.Push(SLEntity.Empty); if (context != null) { context[TokenName] = stack.Peek(); } TokenName = ""; Token.Length = 0; context = stack.Peek(); break; case '}': case ']': if (QuoteMode) { Token.Append(aJSON[i]); break; } if (stack.Count == 0) throw new Exception("JSON Parse: Too many closing brackets\n" + aJSON); stack.Peek().EndModified(); stack.Pop(); if (Token.Length > 0 || TokenIsQuoted) { ParseElement(context, Token.ToString(), TokenName, TokenIsQuoted); TokenIsQuoted = false; } TokenName = ""; Token.Length = 0; if (stack.Count > 0) context = stack.Peek(); break; case ':': if (QuoteMode) { Token.Append(aJSON[i]); break; } TokenName = Token.ToString(); Token.Length = 0; TokenIsQuoted = false; break; case '"': QuoteMode ^= true; TokenIsQuoted |= QuoteMode; break; case ',': if (QuoteMode) { Token.Append(aJSON[i]); break; } if (Token.Length > 0 || TokenIsQuoted) { ParseElement(context, Token.ToString(), TokenName, TokenIsQuoted); } TokenName = ""; Token.Length = 0; TokenIsQuoted = false; break; case '\r': case '\n': break; case ' ': case '\t': if (QuoteMode) Token.Append(aJSON[i]); break; case '\\': ++i; if (QuoteMode) { var C = aJSON[i]; switch (C) { case 't': Token.Append('\t'); break; case 'r': Token.Append('\r'); break; case 'n': Token.Append('\n'); break; case 'b': Token.Append('\b'); break; case 'f': Token.Append('\f'); break; case 'u': { var s = aJSON.Substring(i + 1, 4); Token.Append((char)int.Parse( s, System.Globalization.NumberStyles.AllowHexSpecifier)); i += 4; break; } default: Token.Append(C); break; } } break; default: Token.Append(aJSON[i]); break; } ++i; } if (QuoteMode) { throw new Exception("JSON Parse: Quotation marks seems to be messed up.\n" + aJSON); } return context; } public static SLEntity LoadJson(string v) { var obj = Parse(SLEntity.Empty, v); return obj; } public static SLEntity LoadJson(SLEntity root, string v) { var obj = Parse(root, v); return obj; } public static string SaveToJson(SLEntity entity, int sortDepth = -1, bool indent = false) { if (indent) { var collection = SaveObj(null, entity, new StringBuilder(1024 * 1024 * 5), 0, sortDepth); return collection.ToString(); } else { var collection = SaveObjNoIndent(null, entity, new StringBuilder(1024 * 1024 * 5), 0, sortDepth); return collection.ToString(); } } private static StringBuilder SaveObj(string id, SLEntity entity, StringBuilder builder, int depth, int sortDepth = -1) { if (depth > 0) builder.Append(' ', depth); if (entity.IsValue) { if (entity.IsNumeric) { builder.Append('\"').Append(id).Append("\": ").Append(RoundAndFormat(entity)); } else { builder.Append('\"').Append(id).Append("\": \""); foreach (var ch in entity.ToString()) { if (ch == '"') { builder.Append("\\\""); } else if (ch == '\n') { builder.Append("\\n"); } else { builder.Append(ch); } } builder.Append("\""); } } else { if (depth != 0) { builder.Append('\"').Append(id).Append("\": {\r\n"); } else { builder.Append("{\r\n"); } if (sortDepth != -1 && depth > sortDepth) { var count = entity.Count(); foreach (var child in entity.OrderBy(u => { if (int.TryParse(u.ID, out var numberID)) { return string.Format("{0}{1:0000}", u.IsValue ? 0 : 1, numberID); } return string.Format("{0}{1}", u.IsValue ? 0 : 1, u.ID); })) { SaveObj(child.ID, child, builder, depth + 2, sortDepth); count -= 1; if (count > 0) { builder.Append(",\r\n"); } else { builder.Append("\r\n"); } } } else { var count = entity.Count(); foreach (var child in entity) { SaveObj(child.ID, child, builder, depth + 2, sortDepth); count -= 1; if (count > 0) { builder.Append(",\r\n"); } else { builder.Append("\r\n"); } } } if (depth > 0) builder.Append(' ', depth); builder.Append('}'); } return builder; } private static StringBuilder SaveObjNoIndent(string id, SLEntity entity, StringBuilder builder, int depth, int sortDepth = -1) { if (entity.IsValue) { if (entity.IsNumeric) { //builder.Append('\"').Append(id).Append("\":").Append(RoundAndFormat(entity)); // 인덴트가 없다는건 데이터 전송용이므로 빠르게 진행 builder.Append('\"').Append(id).Append("\":").Append(entity.ToString()); } else { builder.Append('\"').Append(id).Append("\":\""); foreach (var ch in entity.ToString()) { if (ch == '"') { builder.Append("\\\""); } else if (ch == '\n') { builder.Append("\\n"); } else { builder.Append(ch); } } builder.Append("\""); } } else { if (depth != 0) { builder.Append('\"').Append(id).Append("\":{"); } else { builder.Append("{"); } if (sortDepth != -1 && depth > sortDepth) { var count = entity.Count(); foreach (var child in entity.OrderBy(u => { if (int.TryParse(u.ID, out var numberID)) { return string.Format("{0}{1:0000}", u.IsValue ? 0 : 1, numberID); } return string.Format("{0}{1}", u.IsValue ? 0 : 1, u.ID); })) { SaveObjNoIndent(child.ID, child, builder, depth + 2, sortDepth); count -= 1; if (count > 0) { builder.Append(","); } } } else { var count = entity.Count(); foreach (var child in entity) { SaveObjNoIndent(child.ID, child, builder, depth + 2, sortDepth); count -= 1; if (count > 0) { builder.Append(","); } } } builder.Append('}'); } return builder; } public static string RoundAndFormat(SLEntity num) { var str = num.ToString(); var decimalIndex = str.IndexOf('.'); if (decimalIndex >= 0) { // 2자리까지 반올림 대상으로 한다 var roundIndex = str.Length - 2; if (str[roundIndex] != '9' && str[roundIndex] != '0') { roundIndex = str.Length - 3; } var nine = false; if (str[roundIndex] == '9') nine = true; while (roundIndex > decimalIndex && ((nine && str[roundIndex] == '9') || (nine == false && str[roundIndex] == '0'))) { roundIndex--; } if (str.Length > decimalIndex + 6 && roundIndex <= decimalIndex) { // 반올림정수 return ((int)Math.Round((double)num)).ToString(); } if (str.Length < decimalIndex + 6) { return str; // 단순한 소수 } if (roundIndex < str.Length - 6) // 3~4자리 이상 반복 { return string.Format("{0:f" + (roundIndex - decimalIndex) + "}", (double)num); } else { return str; // 복잡한 소수 } } else { return str; // 정수 } } } }