//
// Copyright (c) 2019- yutopp (yutopp@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
//
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.IO;
namespace VJson
{
///
/// Write JSON data to streams as UTF-8.
///
// TODO: Add [Preserve] in Unity
public sealed class JsonWriter : IDisposable
{
struct State
{
public StateKind Kind;
public int Depth;
}
enum StateKind
{
ObjectKeyHead,
ObjectKeyOther,
ObjectValue,
ArrayHead,
ArrayOther,
None,
}
private StreamWriter _writer;
private int _indent;
private string _indentStr = null;
private Stack _states = new Stack();
public JsonWriter(Stream s, int indent = 0)
{
_writer = new StreamWriter(s); // UTF-8 by default
_indent = indent;
if (_indent > 0) {
_indentStr = new String(' ', _indent);
}
_states.Push(new State
{
Kind = StateKind.None,
Depth = 0,
});
}
public void Dispose()
{
if (_writer != null)
{
((IDisposable)_writer).Dispose();
}
}
public void WriteObjectStart()
{
var state = _states.Peek();
if (state.Kind == StateKind.ObjectKeyHead || state.Kind == StateKind.ObjectKeyOther)
{
throw new Exception("");
}
WriteDelimiter();
_writer.Write("{");
_states.Push(new State
{
Kind = StateKind.ObjectKeyHead,
Depth = state.Depth + 1,
});
}
public void WriteObjectKey(string key)
{
var state = _states.Peek();
if (state.Kind != StateKind.ObjectKeyHead && state.Kind != StateKind.ObjectKeyOther)
{
throw new Exception("");
}
WriteValue(key);
_writer.Write(":");
_states.Pop();
_states.Push(new State
{
Kind = StateKind.ObjectValue,
Depth = state.Depth,
});
}
public void WriteObjectEnd()
{
var state = _states.Peek();
if (state.Kind != StateKind.ObjectKeyHead && state.Kind != StateKind.ObjectKeyOther)
{
throw new Exception("");
}
_states.Pop();
if (state.Kind == StateKind.ObjectKeyOther)
{
WriteIndentBreakForHuman(_states.Peek().Depth);
}
_writer.Write("}");
}
public void WriteArrayStart()
{
var state = _states.Peek();
if (state.Kind == StateKind.ObjectKeyHead || state.Kind == StateKind.ObjectKeyOther)
{
throw new Exception("");
}
WriteDelimiter();
_writer.Write("[");
_states.Push(new State
{
Kind = StateKind.ArrayHead,
Depth = state.Depth + 1,
});
}
public void WriteArrayEnd()
{
var state = _states.Peek();
if (state.Kind != StateKind.ArrayHead && state.Kind != StateKind.ArrayOther)
{
throw new Exception("");
}
_states.Pop();
if (state.Kind == StateKind.ArrayOther)
{
WriteIndentBreakForHuman(_states.Peek().Depth);
}
_writer.Write("]");
}
public void WriteValue(bool v)
{
WriteDelimiter();
_writer.Write(v ? "true" : "false");
}
public void WriteValue(byte v)
{
WritePrimitive(v);
}
public void WriteValue(sbyte v)
{
WritePrimitive(v);
}
public void WriteValue(char v)
{
WritePrimitive(v);
}
public void WriteValue(decimal v)
{
WritePrimitive(v);
}
public void WriteValue(double v)
{
WritePrimitive(v);
}
public void WriteValue(float v)
{
WritePrimitive(v);
}
public void WriteValue(int v)
{
WritePrimitive(v);
}
public void WriteValue(uint v)
{
WritePrimitive(v);
}
public void WriteValue(long v)
{
WritePrimitive(v);
}
public void WriteValue(ulong v)
{
WritePrimitive(v);
}
public void WriteValue(short v)
{
WritePrimitive(v);
}
public void WriteValue(ushort v)
{
WritePrimitive(v);
}
public void WriteValue(string v)
{
WriteDelimiter();
_writer.Write('\"');
_writer.Write(Escape(v).ToArray());
_writer.Write('\"');
}
public void WriteValueNull()
{
WriteDelimiter();
_writer.Write("null");
}
void WritePrimitive(char v)
{
WritePrimitive((int)v);
}
void WritePrimitive(float v)
{
WritePrimitive(string.Format(CultureInfo.InvariantCulture, "{0:G9}", v));
}
void WritePrimitive(double v)
{
WritePrimitive(string.Format(CultureInfo.InvariantCulture, "{0:G17}", v));
}
void WritePrimitive(T v)
{
WriteDelimiter();
_writer.Write(v);
}
void WriteIndentBreakForHuman(int depth)
{
if (_indent > 0)
{
_writer.Write('\n');
for (int i = 0; i < depth; ++i)
{
_writer.Write(_indentStr);
}
}
}
void WriteSpaceForHuman()
{
if (_indent > 0)
{
_writer.Write(' ');
}
}
void WriteDelimiter()
{
var state = _states.Peek();
if (state.Kind == StateKind.ArrayHead)
{
WriteIndentBreakForHuman(state.Depth);
_states.Pop();
_states.Push(new State
{
Kind = StateKind.ArrayOther,
Depth = state.Depth
});
return;
}
if (state.Kind == StateKind.ObjectKeyHead)
{
WriteIndentBreakForHuman(state.Depth);
}
if (state.Kind == StateKind.ArrayOther || state.Kind == StateKind.ObjectKeyOther)
{
_writer.Write(",");
WriteIndentBreakForHuman(state.Depth);
}
if (state.Kind == StateKind.ObjectValue)
{
WriteSpaceForHuman();
_states.Pop();
_states.Push(new State
{
Kind = StateKind.ObjectKeyOther,
Depth = state.Depth
});
}
}
IEnumerable Escape(string s)
{
foreach(var c in s)
{
char modified = default(char);
if (c <= 0x20 || c == '\"' || c == '\\')
{
switch(c)
{
case '\"':
modified = '\"';
break;
case '\\':
modified = '\\';
break;
case '\b':
modified = 'b';
break;
case '\n':
modified = 'n';
break;
case '\r':
modified = 'r';
break;
case '\t':
modified = 't';
break;
}
}
if (modified != default(char))
{
yield return '\\';
yield return modified;
}
else
{
yield return c;
}
}
}
}
}