You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
SMX_PGE/smx-config/SMXJSON.cs

532 lines
18 KiB

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
// All C# JSON implementations are either pretty awful or incredibly bloated, so we
// just use our own.
namespace SMXJSON
{
public class JSONError: Exception
{
public JSONError(string error)
{
}
};
public class ParseError: JSONError
{
public ParseError(StringReader reader, string error):
base(error)
{
}
};
public static class ObjectListExtensions
{
public static T Get<T>(this List<object> array, int idx, T defaultValue)
{
if(idx < 0 || idx >= array.Count)
return defaultValue;
object value = array[idx];
if(!typeof(T).IsAssignableFrom(value.GetType()))
return defaultValue;
return (T) value;
}
// Our numbers are always doubles. Add some other basic data types for convenience.
public static int Get(this List<object> array, int idx, int defaultValue)
{
return (int) array.Get(idx, (double) defaultValue);
}
public static Byte Get(this List<object> array, int idx, Byte defaultValue)
{
return (Byte) array.Get(idx, (double) defaultValue);
}
public static float Get(this List<object> array, int idx, float defaultValue)
{
return (float) array.Get(idx, (double) defaultValue);
}
// Return the value of key. If it doesn't exist, or doesn't have the expected
// type, return defaultValue.
public static T Get<T>(this Dictionary<string, Object> dict, string key, T defaultValue)
{
object value;
if(!dict.TryGetValue(key, out value))
return defaultValue;
if(!typeof(T).IsAssignableFrom(value.GetType()))
return defaultValue;
return (T) value;
}
// Set result to the value of key if it exists and has the correct type, and return
// true. Otherwise, leave result unchanged and return false.
public static bool GetValue<T>(this Dictionary<string, Object> dict, string key, ref T result)
{
object value;
if(!dict.TryGetValue(key, out value))
return false;
if(!typeof(T).IsAssignableFrom(result.GetType()))
return false;
result = (T) value;
return true;
}
// Our numbers are always doubles. Add some other basic data types for convenience.
public static int Get(this Dictionary<string, Object> dict, string key, int defaultValue)
{
return (int) dict.Get(key, (double) defaultValue);
}
public static Byte Get(this Dictionary<string, Object> dict, string key, Byte defaultValue)
{
return (Byte) dict.Get(key, (double) defaultValue);
}
public static float Get(this Dictionary<string, Object> dict, string key, float defaultValue)
{
return (float) dict.Get(key, (double) defaultValue);
}
}
class SerializeJSON
{
static public string Serialize(object obj)
{
StringBuilder output = new StringBuilder();
Serialize(obj, output, 0);
output.Append('\n');
return output.ToString();
}
// Add start-of-line indentation.
static private void AddIndent(StringBuilder output, int indent)
{
output.Append(' ', indent*4);
}
// Serialize a boolean.
static private void SerializeObject(bool value, StringBuilder output)
{
if(value)
output.Append("true");
else
output.Append("false");
}
// Serialize a string.
static private void SerializeObject(String str, StringBuilder output)
{
output.Append('"');
foreach(char c in str)
{
switch(c)
{
case '"': output.Append("\\\""); break;
case '\\': output.Append("\\\\"); break;
case '\b': output.Append("\\b"); break;
case '\n': output.Append("\\n"); break;
case '\r': output.Append("\\r"); break;
case '\t': output.Append("\\t"); break;
default:
// We don't escape Unicode. Every sane JSON parser accepts UTF-8.
output.Append(c);
break;
}
}
output.Append('"');
}
// Serialize an array.
static private void SerializeObject<T>(List<T> array, StringBuilder output, int indent)
{
output.Append("[\n");
bool first = true;
indent += 1;
foreach(T element in array)
{
if(first)
first = false;
else
output.Append(",\n");
AddIndent(output, indent);
Serialize(element, output, indent);
}
output.Append('\n');
indent -= 1;
AddIndent(output, indent);
output.Append(']');
}
// Serialize a dictionary.
static private void SerializeObject<T>(Dictionary<string, T> dict, StringBuilder output, int indent)
{
output.Append("{\n");
indent += 1;
bool first = true;
foreach(KeyValuePair<string,T> element in dict)
{
if(first)
first = false;
else
output.Append(",\n");
AddIndent(output, indent);
SerializeObject(element.Key, output);
output.Append(':');
output.Append(' ');
Serialize(element.Value, output, indent);
}
output.Append('\n');
indent -= 1;
AddIndent(output, indent);
output.Append('}');
}
// Serialize an object based on its type.
static public void Serialize(object obj, StringBuilder output, int indent)
{
if(obj == null) { output.Append("null"); return; }
if(typeof(Int32).IsInstanceOfType(obj)) { output.Append(obj.ToString()); return; }
if(typeof(float).IsInstanceOfType(obj)) { output.Append(obj.ToString()); return; }
if(typeof(Double).IsInstanceOfType(obj)) { output.Append(obj.ToString()); return; }
if(typeof(Boolean).IsInstanceOfType(obj)) { SerializeObject((Boolean) obj, output); return; }
if(typeof(string).IsInstanceOfType(obj)) { SerializeObject((string) obj, output); return; }
// C# generics aren't very well designed, so this is clunky. We should be able to cast
// a List<string> to List<object>, but some overzealous language designers thought that
// since that has some unsafe uses, we shouldn't be allowed to use it for perfectly safe
// uses either (eg. read-only access).
if(obj.GetType().GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>)))
{
Type valueType = obj.GetType().GetGenericArguments()[0];
if(valueType == typeof(object)) { SerializeObject((List<object>)obj, output, indent); return; }
if(valueType == typeof(Int32)) { SerializeObject((List<Int32>)obj, output, indent); return; }
if(valueType == typeof(float)) { SerializeObject((List<float>)obj, output, indent); return; }
if(valueType == typeof(Double)) { SerializeObject((List<Double>)obj, output, indent); return; }
if(valueType == typeof(Boolean)) { SerializeObject((List<Boolean>)obj, output, indent); return; }
if(valueType == typeof(string)) { SerializeObject((List<string>)obj, output, indent); return; }
}
if(obj.GetType().GetGenericTypeDefinition().IsAssignableFrom(typeof(Dictionary<,>)))
{
Type keyType = obj.GetType().GetGenericArguments()[0];
if(typeof(string).IsAssignableFrom(keyType))
{
Type valueType = obj.GetType().GetGenericArguments()[1];
if(valueType == typeof(object)) { SerializeObject((Dictionary<string, object>)obj, output, indent); return; }
if(valueType == typeof(Int32)) { SerializeObject((Dictionary<string, Int32>)obj, output, indent); return; }
if(valueType == typeof(float)) { SerializeObject((Dictionary<string, float>)obj, output, indent); return; }
if(valueType == typeof(Double)) { SerializeObject((Dictionary<string, Double>)obj, output, indent); return; }
if(valueType == typeof(Boolean)) { SerializeObject((Dictionary<string, Boolean>)obj, output, indent); return; }
if(valueType == typeof(string)) { SerializeObject((Dictionary<string, string>)obj, output, indent); return; }
}
}
throw new JSONError("Unsupported type: " + obj.GetType());
}
}
class ParseJSON
{
static private void SkipWhitespace(StringReader reader)
{
while(true)
{
int c = reader.Peek();
switch(c)
{
case ' ':
case '\n':
case '\t':
reader.Read();
continue;
default:
return;
}
}
}
// Parse JSON. On error, return null.
public static Object Parse(string json)
{
return ParseWithExceptions(new StringReader(json));
}
// Parse JSON, expecting a specific outer type. On parse error, return a default value.
public static T Parse<T>(string json) where T: new()
{
Object result = Parse(json);
if(!typeof(T).IsAssignableFrom(result.GetType()))
return new T();
return (T) result;
}
// Parse JSON. On error, raise JSONError.
//
// Most of the time, errors aren't expected and putting exception handling around every
// place JSON is parsed can be brittle. Parse() can be used instead to just return
// a default value.
public static Object ParseWithExceptions(StringReader reader)
{
Object result = ParseJSONValue(reader);
SkipWhitespace(reader);
// Other than whitespace, we should be at the end of the file.
if(reader.Read() != -1)
throw new ParseError(reader, "Unexpected data at the end of the string");
return result;
}
private static Object ParseJSONValue(StringReader reader)
{
SkipWhitespace(reader);
int nextCharacter = reader.Peek();
switch(nextCharacter)
{
case '"':
return ReadJSONString(reader);
case '{':
return ReadJSONDictionary(reader);
case '[':
return ReadJSONArray(reader);
}
if(nextCharacter == '-' || (nextCharacter >= '0' && nextCharacter <= '9'))
return ReadJSONNumber(reader);
if(reader.Peek() == 'n')
{
// The only valid value this can be is "null".
Expect(reader, 'n');
Expect(reader, 'u');
Expect(reader, 'l');
Expect(reader, 'l');
return null;
}
if(reader.Peek() == 't')
{
Expect(reader, 't');
Expect(reader, 'r');
Expect(reader, 'u');
Expect(reader, 'e');
return true;
}
if(reader.Peek() == 'f')
{
Expect(reader, 'f');
Expect(reader, 'a');
Expect(reader, 'l');
Expect(reader, 's');
Expect(reader, 'e');
return true;
}
throw new ParseError(reader, "Unexpected token");
}
// Skip whitespace, then read one character, which we expect to have a specific value.
static private void Expect(StringReader reader, char character)
{
SkipWhitespace(reader);
if(reader.Read() != character)
throw new ParseError(reader, "Expected " + character);
}
static private List<Object> ReadJSONArray(StringReader reader)
{
Expect(reader, '[');
List<Object> result = new List<Object>();
while(true)
{
Object value = ParseJSONValue(reader);
result.Add(value);
SkipWhitespace(reader);
int nextCharacter = reader.Read();
switch(nextCharacter)
{
case ']':
return result;
case ',':
continue;
case -1:
throw new ParseError(reader, "Unexpected EOF reading array");
default:
throw new ParseError(reader, "Unexpected token " + nextCharacter + " reading array");
}
}
}
static private Dictionary<string, Object> ReadJSONDictionary(StringReader reader)
{
Expect(reader, '{');
Dictionary<string, Object> result = new Dictionary<string, Object>();
while(true)
{
string key = ReadJSONString(reader);
Expect(reader, ':');
Object value = ParseJSONValue(reader);
result.Add(key, value);
SkipWhitespace(reader);
switch(reader.Read())
{
case '}':
return result;
case ',':
continue;
case -1:
throw new ParseError(reader, "Unexpected EOF reading dictionary");
default:
throw new ParseError(reader, "Unexpected token reading dictionary");
}
}
}
static private string ReadJSONString(StringReader reader)
{
Expect(reader, '"');
StringBuilder result = new StringBuilder();
while(true)
{
int c = reader.Read();
if(c == -1)
throw new ParseError(reader, "Unexpected EOF reading string");
if(c == '"')
break;
// XXX: untested
if(c == '\\')
{
c = reader.Read();
switch(c)
{
case '"':
case '\\':
case '/':
result.Append((char) c);
break;
case 'b': result.Append('\b'); break;
case 'n': result.Append('\n'); break;
case 'r': result.Append('\r'); break;
case 't': result.Append('\t'); break;
case 'u':
{
// Parse a \u1234 escape.
int codePoint = 0;
for(int i = 0; i < 4; ++i)
{
codePoint *= 10;
c = reader.Read();
if(c == -1)
throw new ParseError(reader, "Unexpected EOF reading string");
if(c < '0' || c > '9')
throw new ParseError(reader, "Unexpected token " + c + " reading Unicode escape");
codePoint += c - (int) '0';
}
result.Append((char) codePoint);
break;
}
default:
throw new ParseError(reader, "Unrecognized escape sequence in string: \\" + (char) c);
}
continue;
}
result.Append((char) c);
}
return result.ToString();
}
static private double ReadJSONNumber(StringReader reader)
{
StringBuilder number = new StringBuilder();
bool negative = false;
if(reader.Peek() == '-')
{
negative = true;
reader.Read();
}
int nextCharacter = reader.Read();
if(nextCharacter == '0')
number.Append((char) nextCharacter);
else
{
if(nextCharacter < '1' || nextCharacter > '9')
throw new ParseError(reader, "Unexpected token reading number");
number.Append((char) nextCharacter);
while(reader.Peek() >= '0' && reader.Peek() <= '9')
number.Append((char) reader.Read());
}
if(reader.Peek() == '.')
{
number.Append(reader.Read());
if(reader.Peek() < '0' || reader.Peek() > '9')
throw new ParseError(reader, "Unexpected token reading number");
while(reader.Peek() >= '0' && reader.Peek() <= '9')
number.Append((char) reader.Read());
}
if(reader.Peek() == 'e' || reader.Peek() == 'E')
{
number.Append((char) reader.Read());
if(reader.Peek() == '+' || reader.Peek() == '-')
number.Append((char) reader.Read());
if(reader.Peek() < '0' || reader.Peek() > '9')
throw new ParseError(reader, "Unexpected token reading number");
while(true)
{
nextCharacter = reader.Read();
if(nextCharacter < '0' || nextCharacter > '9')
break;
number.Append((char) nextCharacter);
}
}
double result;
if(!Double.TryParse(number.ToString(), out result))
throw new ParseError(reader, "Unexpected error parsing number \"" + number.ToString() + "\"");
if(negative)
result = -result;
return result;
}
}
}