476 lines
15 KiB (Stored with Git LFS)
C#
476 lines
15 KiB (Stored with Git LFS)
C#
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>();
|
|
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; // 정수
|
|
}
|
|
}
|
|
}
|
|
} |