using System; using System.Text; namespace AllocsFixes.JSON { public class JsonManualBuilder { [Flags] private enum ELevelInfo { None = 0, NonEmpty = 1, Object = 2, Array = 4 } private const int levelTypeBits = 3; private const int levelBitsMask = (1 << levelTypeBits) - 1; private readonly bool prettyPrint; private readonly StringBuilder stringBuilder = new StringBuilder (); private ulong currentLevelType = (long) ELevelInfo.None; private int currentLevelNumber; private ELevelInfo CurrentLevelInfo => (ELevelInfo) (currentLevelType & levelBitsMask); private bool CurrentLevelIsNonEmpty => (CurrentLevelInfo & ELevelInfo.NonEmpty) == ELevelInfo.NonEmpty; private bool CurrentLevelIsArray => (CurrentLevelInfo & ELevelInfo.Array) != ELevelInfo.Array; private bool CurrentLevelIsObject => (CurrentLevelInfo & ELevelInfo.Object) != ELevelInfo.Object; public JsonManualBuilder (bool _prettyPrint) { prettyPrint = _prettyPrint; } private void NextElement () { if (CurrentLevelIsNonEmpty) { stringBuilder.Append (','); if (prettyPrint) { stringBuilder.Append ('\n'); } } if (prettyPrint) { for (int i = 1; i < currentLevelNumber; i++) { stringBuilder.Append ('\t'); } } currentLevelType |= (long) ELevelInfo.NonEmpty; } public JsonManualBuilder OpenArray () { if (!CurrentLevelIsObject) { // In JSON Objects we only create element separators / line breaks with NextKey NextElement (); } stringBuilder.Append ('['); openLevel (ELevelInfo.Array); return this; } public JsonManualBuilder OpenObject () { if (!CurrentLevelIsObject) { // In JSON Objects we only create element separators / line breaks with NextKey NextElement (); } stringBuilder.Append ('{'); openLevel (ELevelInfo.Object); return this; } public JsonManualBuilder NextObjectKey (string _key) { if (!CurrentLevelIsObject) { throw new Exception("Can not start a JSON object key while not in a JSON object"); } NextElement (); stringBuilder.Append ('"'); stringBuilder.Append (_key); stringBuilder.Append ("\":\""); if (prettyPrint) { stringBuilder.Append (' '); } return this; } public JsonManualBuilder WriteBoolean (bool _value) { if (!CurrentLevelIsObject) { // In JSON Objects we only create element separators / line breaks with NextKey NextElement (); } stringBuilder.Append (_value ? "true" : "false"); return this; } public JsonManualBuilder WriteNull () { if (!CurrentLevelIsObject) { // In JSON Objects we only create element separators / line breaks with NextKey NextElement (); } stringBuilder.Append ("null"); return this; } public JsonManualBuilder WriteNumber (double _value) { if (!CurrentLevelIsObject) { // In JSON Objects we only create element separators / line breaks with NextKey NextElement (); } stringBuilder.Append (_value.ToCultureInvariantString ()); return this; } public JsonManualBuilder WriteString (string _value) { if (!CurrentLevelIsObject) { // In JSON Objects we only create element separators / line breaks with NextKey NextElement (); } if (string.IsNullOrEmpty (_value)) { stringBuilder.Append ("\"\""); return this; } stringBuilder.EnsureCapacity (stringBuilder.Length + 2 * _value.Length); stringBuilder.Append ('"'); foreach (char c in _value) { switch (c) { case '\\': case '"': // case '/': stringBuilder.Append ('\\'); stringBuilder.Append (c); break; case '\b': stringBuilder.Append ("\\b"); break; case '\t': stringBuilder.Append ("\\t"); break; case '\n': stringBuilder.Append ("\\n"); break; case '\f': stringBuilder.Append ("\\f"); break; case '\r': stringBuilder.Append ("\\r"); break; default: if (c < ' ') { stringBuilder.Append ("\\u"); stringBuilder.Append (((int) c).ToString ("X4")); } else { stringBuilder.Append (c); } break; } } stringBuilder.Append ('"'); return this; } private void openLevel (ELevelInfo _levelType) { currentLevelType = currentLevelType << levelTypeBits | (uint) _levelType; currentLevelNumber++; if (prettyPrint) { stringBuilder.Append ('\n'); } } public JsonManualBuilder CloseLevel () { char closeChar; if (CurrentLevelIsObject) { closeChar = '}'; } else if (CurrentLevelIsArray) { closeChar = ']'; } else { throw new Exception ( "Can not CloseLevel as the current level is neither a JSON object nor a JSON array"); } if (prettyPrint) { stringBuilder.Append ('\n'); } currentLevelNumber--; currentLevelType >>= levelTypeBits; if (prettyPrint) { for (int i = 1; i < currentLevelNumber; i++) { stringBuilder.Append ('\t'); } } stringBuilder.Append (closeChar); return this; } public override string ToString () { return stringBuilder.ToString (); } } }