287 lines
8.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
// ReSharper disable once CheckNamespace
namespace GameDevWare.Serialization
{
public abstract class JsonWriter : IJsonWriter
{
public const int DEFAULT_BUFFER_SIZE = 1024;
[Flags]
private enum Structure : byte
{
IsContainer = 0x1,
IsObject = 0x2 | IsContainer,
IsArray = 0x4 | IsContainer,
IsStartOfStructure = 0x1 << 7,
IsStartOfContainer = IsContainer | IsStartOfStructure
}
private const long JS_NUMBER_MAX_VALUE_INT64 = 9007199254740992L;
private const ulong JS_NUMBER_MAX_VALUE_U_INT64 = 9007199254740992UL;
private const double JS_NUMBER_MAX_VALUE_DOUBLE = 9007199254740992.0;
private const double JS_NUMBER_MAX_VALUE_SINGLE = 9007199254740992.0f;
private const decimal JS_NUMBER_MAX_VALUE_DECIMAL = 9007199254740992.0m;
private static readonly char[] Tabs = new char[] { '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t', '\t' };
private static readonly char[] Newline = "\r\n".ToCharArray();
private static readonly char[] NameSeparator = ":".ToCharArray();
private static readonly char[] ValueSeparator = ",".ToCharArray();
private static readonly char[] ArrayBegin = "[".ToCharArray();
private static readonly char[] ArrayEnd = "]".ToCharArray();
private static readonly char[] ObjectBegin = "{".ToCharArray();
private static readonly char[] ObjectEnd = "}".ToCharArray();
private static readonly char[] Null = "null".ToCharArray();
private static readonly char[] True = "true".ToCharArray();
private static readonly char[] False = "false".ToCharArray();
private readonly Stack<Structure> structStack;
private readonly char[] buffer;
public SerializationContext Context { get; private set; }
public long CharactersWritten { get; protected set; }
public int InitialPadding { get; set; }
protected JsonWriter(SerializationContext context, char[] buffer = null)
{
if (context == null) throw new ArgumentNullException("context");
if (buffer != null && buffer.Length < 1024) throw new ArgumentOutOfRangeException("buffer", "Buffer should be at least 1024 bytes long.");
this.Context = context;
this.buffer = buffer ?? new char[DEFAULT_BUFFER_SIZE];
this.structStack = new Stack<Structure>(10);
}
public abstract void Flush();
public abstract void WriteJson(string jsonString);
public abstract void WriteJson(char[] jsonString, int offset, int charactersToWrite);
public void Write(string value)
{
if (value == null)
{
this.WriteNull();
return;
}
this.WriteFormatting(JsonToken.StringLiteral);
var len = value.Length;
var offset = 0;
this.buffer[0] = '"';
this.WriteJson(this.buffer, 0, 1);
while (offset < len)
{
var writtenInBuffer = JsonUtils.EscapeBuffer(value, ref offset, this.buffer, 0);
this.WriteJson(this.buffer, 0, writtenInBuffer);
}
this.buffer[0] = '"';
this.WriteJson(this.buffer, 0, 1);
}
public void Write(JsonMember member)
{
this.WriteFormatting(JsonToken.Member);
if (member.IsEscapedAndQuoted)
{
if (member.NameString != null)
this.WriteJson(member.NameString);
else
this.WriteJson(member.NameChars, 0, member.NameChars.Length);
}
else
{
if (member.NameString != null)
this.WriteString(member.NameString);
else
this.WriteString(new string(member.NameChars));
this.WriteJson(NameSeparator, 0, NameSeparator.Length);
}
}
public void Write(int number)
{
this.WriteFormatting(JsonToken.Number);
var len = JsonUtils.Int32ToBuffer(number, this.buffer, 0, this.Context.Format);
this.WriteJson(this.buffer, 0, len);
}
public void Write(uint number)
{
this.WriteFormatting(JsonToken.Number);
var len = JsonUtils.UInt32ToBuffer(number, this.buffer, 0, this.Context.Format);
this.WriteJson(this.buffer, 0, len);
}
public void Write(long number)
{
this.WriteFormatting(JsonToken.Number);
var len = JsonUtils.Int64ToBuffer(number, this.buffer, 0, this.Context.Format);
if (number > JS_NUMBER_MAX_VALUE_INT64)
this.WriteString(new string(this.buffer, 0, len));
else
this.WriteJson(this.buffer, 0, len);
}
public void Write(ulong number)
{
this.WriteFormatting(JsonToken.Number);
var len = JsonUtils.UInt64ToBuffer(number, this.buffer, 0, this.Context.Format);
if (number > JS_NUMBER_MAX_VALUE_U_INT64)
this.WriteString(new string(this.buffer, 0, len));
else
this.WriteJson(this.buffer, 0, len);
}
public void Write(float number)
{
this.WriteFormatting(JsonToken.Number);
var len = JsonUtils.SingleToBuffer(number, this.buffer, 0, this.Context.Format);
if (number > JS_NUMBER_MAX_VALUE_SINGLE)
this.WriteString(new string(this.buffer, 0, len));
else
this.WriteJson(this.buffer, 0, len);
}
public void Write(double number)
{
this.WriteFormatting(JsonToken.Number);
var len = JsonUtils.DoubleToBuffer(number, this.buffer, 0, this.Context.Format);
if (number > JS_NUMBER_MAX_VALUE_DOUBLE)
this.WriteString(new string(this.buffer, 0, len));
else
this.WriteJson(this.buffer, 0, len);
}
public void Write(decimal number)
{
this.WriteFormatting(JsonToken.Number);
var len = JsonUtils.DecimalToBuffer(number, this.buffer, 0, this.Context.Format);
if (number > JS_NUMBER_MAX_VALUE_DECIMAL)
this.WriteString(new string(this.buffer, 0, len));
else
this.WriteJson(this.buffer, 0, len);
}
public void Write(DateTime dateTime)
{
this.WriteFormatting(JsonToken.DateTime);
if (dateTime.Kind == DateTimeKind.Unspecified)
dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Utc);
var dateTimeFormat = this.Context.DateTimeFormats.FirstOrDefault() ?? "o";
if (dateTimeFormat.IndexOf('z') >= 0 && dateTime.Kind != DateTimeKind.Local)
dateTime = dateTime.ToLocalTime();
var dateString = dateTime.ToString(dateTimeFormat, this.Context.Format);
this.Write(dateString);
}
public void Write(DateTimeOffset dateTimeOffset)
{
this.WriteFormatting(JsonToken.DateTime);
var dateTimeFormat = this.Context.DateTimeFormats.FirstOrDefault() ?? "o";
var dateString = dateTimeOffset.ToString(dateTimeFormat, this.Context.Format);
this.Write(dateString);
}
public void Write(bool value)
{
this.WriteFormatting(JsonToken.Boolean);
if (value)
this.WriteJson(True, 0, True.Length);
else
this.WriteJson(False, 0, False.Length);
}
public void WriteObjectBegin(int numberOfMembers)
{
this.WriteFormatting(JsonToken.BeginObject);
this.structStack.Push(Structure.IsObject | Structure.IsStartOfStructure);
this.WriteJson(ObjectBegin, 0, ObjectBegin.Length);
}
public void WriteObjectEnd()
{
this.WriteFormatting(JsonToken.EndOfObject);
this.structStack.Pop();
this.WriteNewlineAndPad(0);
this.WriteJson(ObjectEnd, 0, ObjectEnd.Length);
}
public void WriteArrayBegin(int numberOfMembers)
{
this.WriteFormatting(JsonToken.BeginArray);
this.structStack.Push(Structure.IsArray | Structure.IsStartOfStructure);
this.WriteJson(ArrayBegin, 0, ArrayBegin.Length);
}
public void WriteArrayEnd()
{
this.WriteFormatting(JsonToken.EndOfArray);
this.structStack.Pop();
this.WriteJson(ArrayEnd, 0, ArrayEnd.Length);
}
public void WriteNull()
{
this.WriteFormatting(JsonToken.Null);
this.WriteJson(Null, 0, Null.Length);
}
public void Reset()
{
this.CharactersWritten = 0;
this.structStack.Clear();
}
private void WriteNewlineAndPad(int correction)
{
if ((this.Context.Options & SerializationOptions.PrettyPrint) != SerializationOptions.PrettyPrint)
return;
// add padings and linebreaks
this.WriteJson(Newline, 0, Newline.Length);
var tabs = this.structStack.Count + correction;
while (tabs > 0)
{
this.WriteJson(Tabs, 0, Math.Min(tabs, Tabs.Length));
tabs -= Tabs.Length;
}
}
private void WriteFormatting(JsonToken token)
{
if (this.structStack.Count <= 0)
return;
var stackPeek = this.structStack.Peek();
var isNotMemberValue = ((stackPeek & Structure.IsObject) != Structure.IsObject || token == JsonToken.Member);
var isEndToken = token == JsonToken.EndOfArray || token == JsonToken.EndOfObject;
if ((stackPeek & Structure.IsContainer) != Structure.IsContainer || !isNotMemberValue)
return;
// it's a begining of container we add padding and remove "is begining" flag
if ((stackPeek & Structure.IsStartOfContainer) == Structure.IsStartOfContainer)
{
stackPeek = this.structStack.Pop();
this.structStack.Push(stackPeek ^ Structure.IsStartOfStructure); // revert "is begining"
}
// else if it's new array's value or new object's member put comman and padding
else if (!isEndToken)
this.WriteJson(ValueSeparator, 0, ValueSeparator.Length);
// padding
// pad only before member in object container(not before value, it's ugly)
this.WriteNewlineAndPad(this.InitialPadding + (isEndToken ? -1 : 0));
}
}
}