using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Text; internal static class BriskJson { public static object Deserialize(string json) { if (string.IsNullOrWhiteSpace(json)) { return null; } return Parser.Parse(json); } public static string Serialize(object value) { return Serializer.Serialize(value); } private sealed class Parser : IDisposable { private readonly StringReader _reader; private Parser(string json) { _reader = new StringReader(json); } public static object Parse(string json) { using (var parser = new Parser(json)) { return parser.ParseValue(); } } public void Dispose() { _reader.Dispose(); } private object ParseValue() { EatWhitespace(); if (_reader.Peek() == -1) { return null; } switch (PeekChar) { case '{': return ParseObject(); case '[': return ParseArray(); case '"': return ParseString(); case 't': return ParseTrue(); case 'f': return ParseFalse(); case 'n': return ParseNull(); default: return ParseNumber(); } } private Dictionary ParseObject() { var table = new Dictionary(); ReadChar(); while (true) { EatWhitespace(); if (PeekChar == '}') { ReadChar(); return table; } var key = ParseString(); EatWhitespace(); ReadChar(); var value = ParseValue(); table[key] = value; EatWhitespace(); if (PeekChar == ',') { ReadChar(); continue; } if (PeekChar == '}') { ReadChar(); return table; } } } private List ParseArray() { var array = new List(); ReadChar(); while (true) { EatWhitespace(); if (PeekChar == ']') { ReadChar(); return array; } array.Add(ParseValue()); EatWhitespace(); if (PeekChar == ',') { ReadChar(); continue; } if (PeekChar == ']') { ReadChar(); return array; } } } private string ParseString() { var builder = new StringBuilder(); ReadChar(); while (true) { if (_reader.Peek() == -1) { break; } var ch = ReadChar(); if (ch == '"') { break; } if (ch == '\\') { if (_reader.Peek() == -1) { break; } ch = ReadChar(); switch (ch) { case '"': case '\\': case '/': builder.Append(ch); break; case 'b': builder.Append('\b'); break; case 'f': builder.Append('\f'); break; case 'n': builder.Append('\n'); break; case 'r': builder.Append('\r'); break; case 't': builder.Append('\t'); break; case 'u': builder.Append((char)Convert.ToInt32(ReadChars(4), 16)); break; } continue; } builder.Append(ch); } return builder.ToString(); } private object ParseNumber() { var token = NextToken(); if (token.IndexOf('.') >= 0 || token.IndexOf('e') >= 0 || token.IndexOf('E') >= 0) { double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue); return doubleValue; } long.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue); return longValue; } private bool ParseTrue() { ReadChars(4); return true; } private bool ParseFalse() { ReadChars(5); return false; } private object ParseNull() { ReadChars(4); return null; } private string NextToken() { var builder = new StringBuilder(); while (_reader.Peek() != -1) { var ch = PeekChar; if (char.IsWhiteSpace(ch) || ch == ',' || ch == ']' || ch == '}') { break; } builder.Append(ReadChar()); } return builder.ToString(); } private void EatWhitespace() { while (_reader.Peek() != -1 && char.IsWhiteSpace(PeekChar)) { _reader.Read(); } } private string ReadChars(int count) { var buffer = new char[count]; _reader.Read(buffer, 0, count); return new string(buffer); } private char PeekChar => Convert.ToChar(_reader.Peek()); private char ReadChar() => Convert.ToChar(_reader.Read()); } private static class Serializer { public static string Serialize(object value) { var builder = new StringBuilder(); SerializeValue(value, builder); return builder.ToString(); } private static void SerializeValue(object value, StringBuilder builder) { if (value == null) { builder.Append("null"); return; } if (value is string str) { SerializeString(str, builder); return; } if (value is bool boolean) { builder.Append(boolean ? "true" : "false"); return; } if (value is IDictionary dictionary) { SerializeObject(dictionary, builder); return; } if (value is IEnumerable enumerable && !(value is string)) { SerializeArray(enumerable, builder); return; } if (IsNumeric(value)) { builder.Append(Convert.ToString(value, CultureInfo.InvariantCulture)); return; } SerializeObject(ToReflectionDictionary(value), builder); } private static void SerializeObject(IDictionary dictionary, StringBuilder builder) { var first = true; builder.Append('{'); foreach (DictionaryEntry entry in dictionary) { if (!first) { builder.Append(','); } SerializeString(Convert.ToString(entry.Key, CultureInfo.InvariantCulture), builder); builder.Append(':'); SerializeValue(entry.Value, builder); first = false; } builder.Append('}'); } private static void SerializeArray(IEnumerable array, StringBuilder builder) { var first = true; builder.Append('['); foreach (var item in array) { if (!first) { builder.Append(','); } SerializeValue(item, builder); first = false; } builder.Append(']'); } private static void SerializeString(string value, StringBuilder builder) { builder.Append('"'); foreach (var ch in value) { switch (ch) { case '"': builder.Append("\\\""); break; case '\\': builder.Append("\\\\"); break; case '\b': builder.Append("\\b"); break; case '\f': builder.Append("\\f"); break; case '\n': builder.Append("\\n"); break; case '\r': builder.Append("\\r"); break; case '\t': builder.Append("\\t"); break; default: if (ch < 32) { builder.Append("\\u"); builder.Append(((int)ch).ToString("x4")); } else { builder.Append(ch); } break; } } builder.Append('"'); } private static bool IsNumeric(object value) { switch (Type.GetTypeCode(value.GetType())) { case TypeCode.Byte: case TypeCode.SByte: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Decimal: case TypeCode.Double: case TypeCode.Single: return true; default: return false; } } private static IDictionary ToReflectionDictionary(object value) { var result = new Dictionary(); var type = value.GetType(); foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public)) { result[field.Name] = field.GetValue(value); } foreach (var property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)) { if (!property.CanRead || property.GetIndexParameters().Length > 0) { continue; } result[property.Name] = property.GetValue(value, null); } return result; } } }