using System;
using System.Globalization;
using System.IO;
using System.Text;
namespace FullSerializer {
public static class fsJsonPrinter {
///
/// Inserts the given number of indents into the builder.
///
private static void InsertSpacing(TextWriter stream, int count) {
for (int i = 0; i < count; ++i) {
stream.Write(" ");
}
}
///
/// Escapes a string.
///
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;
}
}
}
///
/// Formats this data into the given builder.
///
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;
}
}
///
/// Writes the pretty JSON output data to the given stream.
///
/// The data to print.
/// Where to write the printed data.
public static void PrettyJson(fsData data, TextWriter outputStream) {
BuildPrettyString(data, outputStream, 0);
}
///
/// Returns the data in a pretty printed JSON format.
///
public static string PrettyJson(fsData data) {
var sb = new StringBuilder();
using (var writer = new StringWriter(sb)) {
BuildPrettyString(data, writer, 0);
return sb.ToString();
}
}
///
/// Writes the compressed JSON output data to the given stream.
///
/// The data to print.
/// Where to write the printed data.
public static void CompressedJson(fsData data, StreamWriter outputStream) {
BuildCompressedString(data, outputStream);
}
///
/// Returns the data in a relatively compressed JSON format.
///
public static string CompressedJson(fsData data) {
var sb = new StringBuilder();
using (var writer = new StringWriter(sb)) {
BuildCompressedString(data, writer);
return sb.ToString();
}
}
///
/// Utility method that converts a double to a string.
///
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;
}
}
}