using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Kawa.Json
{
[Flags]
public enum FoldMode
{
///
/// Don't fold anything.
///
None = 0,
///
/// Fold short arrays into a single line.
///
Arrays = 1,
///
/// Fold short objects into a single line.
///
Objects = 2,
///
/// Fold both short arrays and short objects.
///
Both = 3,
}
[Serializable]
public sealed class JsonException : Exception
{
public JsonException(string message)
: base(message)
{
}
public JsonException(string message, int line)
: base(string.Format("{0} at line {1}.", message, line))
{ }
}
[Serializable]
public partial class JsonObj : Dictionary
{
public JsonObj() : base()
{ }
protected JsonObj(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context)
{ }
}
public static partial class Json5
{
///
/// The JSON standard does not normally allow NaN or Infinity values. JSON5 does.
///
public static bool AllowNaN { get; set; }
///
/// The JSON standard does not allow trailing commas. JSON5 does.
///
public static bool AllowTrailingComma { get; set; }
///
/// The JSON standard does not allow unquoted keys. JSON5 does, if the keys are valid identifiers.
///
public static bool AllowBareKeys { get; set; }
///
/// How to handle short arrays and objects.
///
public static FoldMode FoldMode { get; set; }
public static int FoldLength { get; set; }
static Json5()
{
FoldLength = 70;
FoldMode = FoldMode.Both;
}
///
/// Parses an input string into an object. The input can be any well-formed JSON or JSON5.
///
/// The string to parse.
/// A JsonObj, list, string, double...
public static object Parse(string text)
{
var previousCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
var at = 0; //The index of the current character
var ch = ' '; //The current character
var escapee = new Dictionary()
{
{ '\'', "\'" },
{ '\"', "\"" },
{ '\\', "\\" },
{ '/', "/" },
{ '\n', string.Empty }, //Replace escaped newlines in strings w/ empty string
{ 'b', "\b" },
{ 'f', "\f" },
{ 'n', "\n" },
{ 'r', "\r" },
{ 't', "\t" },
};
Func locateError = () =>
{
var line = 0;
for (var i = 0; i < at; i++)
{
if (text[i] == '\r')
line++;
}
return line;
};
Func next = () =>
{
if (at >= text.Length)
ch = '\0';
else
{
ch = text[at];
at++;
}
return ch;
};
Func expect = (c) =>
{
if (c != ch)
throw new JsonException(string.Format("Expected '{1}' instead of '{0}'", ch, c), locateError());
return next();
};
#region identifier
Func identifier = () =>
{
var key = new StringBuilder();
key.Append(ch);
//Identifiers must start with a letter, _ or $.
if ((ch != '_' && ch != '$') &&
(ch < 'a' || ch > 'z') &&
(ch < 'A' || ch > 'Z'))
throw new JsonException("Bad identifier", locateError());
//Subsequent characters can contain digits.
while (next() != '\0' && (
ch == '_' || ch == '$' ||
(ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9')))
key.Append(ch);
return key.ToString();
};
#endregion
#region number
Func