287 lines
10 KiB
C#
287 lines
10 KiB
C#
|
using System;
|
|||
|
using System.Globalization;
|
|||
|
using System.IO;
|
|||
|
using System.Text;
|
|||
|
|
|||
|
namespace FullSerializer {
|
|||
|
public static class fsJsonPrinter {
|
|||
|
/// <summary>
|
|||
|
/// Inserts the given number of indents into the builder.
|
|||
|
/// </summary>
|
|||
|
private static void InsertSpacing(TextWriter stream, int count) {
|
|||
|
for (int i = 0; i < count; ++i) {
|
|||
|
stream.Write(" ");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Escapes a string.
|
|||
|
/// </summary>
|
|||
|
private static string EscapeString(string str) {
|
|||
|
// Escaping a string is pretty allocation heavy, so we try hard to not do it.
|
|||
|
|
|||
|
bool needsEscape = false;
|
|||
|
for (int i = 0; i < str.Length; ++i) {
|
|||
|
char c = str[i];
|
|||
|
|
|||
|
// unicode code point
|
|||
|
int intChar = Convert.ToInt32(c);
|
|||
|
if (intChar < 0 || intChar > 127) {
|
|||
|
needsEscape = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// standard escape character
|
|||
|
switch (c) {
|
|||
|
case '"':
|
|||
|
case '\\':
|
|||
|
case '\a':
|
|||
|
case '\b':
|
|||
|
case '\f':
|
|||
|
case '\n':
|
|||
|
case '\r':
|
|||
|
case '\t':
|
|||
|
case '\0':
|
|||
|
needsEscape = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
if (needsEscape) {
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (needsEscape == false) {
|
|||
|
return str;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
StringBuilder result = new StringBuilder();
|
|||
|
|
|||
|
for (int i = 0; i < str.Length; ++i) {
|
|||
|
char c = str[i];
|
|||
|
|
|||
|
// unicode code point
|
|||
|
int intChar = Convert.ToInt32(c);
|
|||
|
if (intChar < 0 || intChar > 127) {
|
|||
|
result.Append(string.Format("\\u{0:x4} ", intChar).Trim());
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
// standard escape character
|
|||
|
switch (c) {
|
|||
|
case '"': result.Append("\\\""); continue;
|
|||
|
case '\\': result.Append(@"\\"); continue;
|
|||
|
case '\a': result.Append(@"\a"); continue;
|
|||
|
case '\b': result.Append(@"\b"); continue;
|
|||
|
case '\f': result.Append(@"\f"); continue;
|
|||
|
case '\n': result.Append(@"\n"); continue;
|
|||
|
case '\r': result.Append(@"\r"); continue;
|
|||
|
case '\t': result.Append(@"\t"); continue;
|
|||
|
case '\0': result.Append(@"\0"); continue;
|
|||
|
}
|
|||
|
|
|||
|
// no escaping needed
|
|||
|
result.Append(c);
|
|||
|
}
|
|||
|
return result.ToString();
|
|||
|
}
|
|||
|
|
|||
|
private static void BuildCompressedString(fsData data, TextWriter stream) {
|
|||
|
switch (data.Type) {
|
|||
|
case fsDataType.Null:
|
|||
|
stream.Write("null");
|
|||
|
break;
|
|||
|
|
|||
|
case fsDataType.Boolean:
|
|||
|
if (data.AsBool) stream.Write("true");
|
|||
|
else stream.Write("false");
|
|||
|
break;
|
|||
|
|
|||
|
case fsDataType.Double:
|
|||
|
// doubles must *always* include a decimal
|
|||
|
stream.Write(ConvertDoubleToString(data.AsDouble));
|
|||
|
break;
|
|||
|
|
|||
|
case fsDataType.Int64:
|
|||
|
stream.Write(data.AsInt64);
|
|||
|
break;
|
|||
|
|
|||
|
case fsDataType.String:
|
|||
|
stream.Write('"');
|
|||
|
stream.Write(EscapeString(data.AsString));
|
|||
|
stream.Write('"');
|
|||
|
break;
|
|||
|
|
|||
|
case fsDataType.Object: {
|
|||
|
stream.Write('{');
|
|||
|
bool comma = false;
|
|||
|
foreach (var entry in data.AsDictionary) {
|
|||
|
if (comma) stream.Write(',');
|
|||
|
comma = true;
|
|||
|
stream.Write('"');
|
|||
|
stream.Write(entry.Key);
|
|||
|
stream.Write('"');
|
|||
|
stream.Write(":");
|
|||
|
BuildCompressedString(entry.Value, stream);
|
|||
|
}
|
|||
|
stream.Write('}');
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case fsDataType.Array: {
|
|||
|
stream.Write('[');
|
|||
|
bool comma = false;
|
|||
|
foreach (var entry in data.AsList) {
|
|||
|
if (comma) stream.Write(',');
|
|||
|
comma = true;
|
|||
|
BuildCompressedString(entry, stream);
|
|||
|
}
|
|||
|
stream.Write(']');
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Formats this data into the given builder.
|
|||
|
/// </summary>
|
|||
|
private static void BuildPrettyString(fsData data, TextWriter stream, int depth) {
|
|||
|
switch (data.Type) {
|
|||
|
case fsDataType.Null:
|
|||
|
stream.Write("null");
|
|||
|
break;
|
|||
|
|
|||
|
case fsDataType.Boolean:
|
|||
|
if (data.AsBool) stream.Write("true");
|
|||
|
else stream.Write("false");
|
|||
|
break;
|
|||
|
|
|||
|
case fsDataType.Double:
|
|||
|
stream.Write(ConvertDoubleToString(data.AsDouble));
|
|||
|
break;
|
|||
|
|
|||
|
case fsDataType.Int64:
|
|||
|
stream.Write(data.AsInt64);
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case fsDataType.String:
|
|||
|
stream.Write('"');
|
|||
|
stream.Write(EscapeString(data.AsString));
|
|||
|
stream.Write('"');
|
|||
|
break;
|
|||
|
|
|||
|
case fsDataType.Object: {
|
|||
|
stream.Write('{');
|
|||
|
stream.WriteLine();
|
|||
|
bool comma = false;
|
|||
|
foreach (var entry in data.AsDictionary) {
|
|||
|
if (comma) {
|
|||
|
stream.Write(',');
|
|||
|
stream.WriteLine();
|
|||
|
}
|
|||
|
comma = true;
|
|||
|
InsertSpacing(stream, depth + 1);
|
|||
|
stream.Write('"');
|
|||
|
stream.Write(entry.Key);
|
|||
|
stream.Write('"');
|
|||
|
stream.Write(": ");
|
|||
|
BuildPrettyString(entry.Value, stream, depth + 1);
|
|||
|
}
|
|||
|
stream.WriteLine();
|
|||
|
InsertSpacing(stream, depth);
|
|||
|
stream.Write('}');
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case fsDataType.Array:
|
|||
|
// special case for empty lists; we don't put an empty line between the brackets
|
|||
|
if (data.AsList.Count == 0) {
|
|||
|
stream.Write("[]");
|
|||
|
}
|
|||
|
|
|||
|
else {
|
|||
|
bool comma = false;
|
|||
|
|
|||
|
stream.Write('[');
|
|||
|
stream.WriteLine();
|
|||
|
foreach (var entry in data.AsList) {
|
|||
|
if (comma) {
|
|||
|
stream.Write(',');
|
|||
|
stream.WriteLine();
|
|||
|
}
|
|||
|
comma = true;
|
|||
|
InsertSpacing(stream, depth + 1);
|
|||
|
BuildPrettyString(entry, stream, depth + 1);
|
|||
|
}
|
|||
|
stream.WriteLine();
|
|||
|
InsertSpacing(stream, depth);
|
|||
|
stream.Write(']');
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Writes the pretty JSON output data to the given stream.
|
|||
|
/// </summary>
|
|||
|
/// <param name="data">The data to print.</param>
|
|||
|
/// <param name="outputStream">Where to write the printed data.</param>
|
|||
|
public static void PrettyJson(fsData data, TextWriter outputStream) {
|
|||
|
BuildPrettyString(data, outputStream, 0);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the data in a pretty printed JSON format.
|
|||
|
/// </summary>
|
|||
|
public static string PrettyJson(fsData data) {
|
|||
|
var sb = new StringBuilder();
|
|||
|
using (var writer = new StringWriter(sb)) {
|
|||
|
BuildPrettyString(data, writer, 0);
|
|||
|
return sb.ToString();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Writes the compressed JSON output data to the given stream.
|
|||
|
/// </summary>
|
|||
|
/// <param name="data">The data to print.</param>
|
|||
|
/// <param name="outputStream">Where to write the printed data.</param>
|
|||
|
public static void CompressedJson(fsData data, StreamWriter outputStream) {
|
|||
|
BuildCompressedString(data, outputStream);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Returns the data in a relatively compressed JSON format.
|
|||
|
/// </summary>
|
|||
|
public static string CompressedJson(fsData data) {
|
|||
|
var sb = new StringBuilder();
|
|||
|
using (var writer = new StringWriter(sb)) {
|
|||
|
BuildCompressedString(data, writer);
|
|||
|
return sb.ToString();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Utility method that converts a double to a string.
|
|||
|
/// </summary>
|
|||
|
private static string ConvertDoubleToString(double d) {
|
|||
|
if (Double.IsInfinity(d) || Double.IsNaN(d))
|
|||
|
return d.ToString(CultureInfo.InvariantCulture);
|
|||
|
|
|||
|
string doubledString = d.ToString(CultureInfo.InvariantCulture);
|
|||
|
|
|||
|
// NOTE/HACK: If we don't serialize with a period or an exponent,
|
|||
|
// then the number will be deserialized as an Int64, not a double.
|
|||
|
if (doubledString.Contains(".") == false &&
|
|||
|
doubledString.Contains("e") == false &&
|
|||
|
doubledString.Contains("E") == false) {
|
|||
|
doubledString += ".0";
|
|||
|
}
|
|||
|
|
|||
|
return doubledString;
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
}
|