Archive Browser
Download AzureTranslationDemoV3.zip, last updated 11/04/2019 (45.48 KB)
Download- md5: 71243a5a58a5b2055e7ffbc8a12dbbbb
- sha1: b3ea3e69c98e9179121e56912434d6ffa37ed7e9
- sha256: 5a4c764a08816a7bfc3060ba0006883bf49134838680b88901018ef707a23650
// PetaJson v0.5 - A simple but flexible Json library in a single .cs file.
//
// Copyright (C) 2014 Topten Software (contact@toptensoftware.com) All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product
// except in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
// Define PETAJSON_NO_DYNAMIC to disable Expando support
// Define PETAJSON_NO_EMIT to disable Reflection.Emit
// Define PETAJSON_NO_DATACONTRACT to disable support for [DataContract]/[DataMember]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Globalization;
using System.Collections;
using System.Threading;
#if !PETAJSON_NO_DYNAMIC
using System.Dynamic;
#endif
#if !PETAJSON_NO_EMIT
using System.Reflection.Emit;
#endif
#if !PETAJSON_NO_DATACONTRACT
using System.Runtime.Serialization;
#endif
namespace PetaJson
{
// Pass to format/write/parse functions to override defaults
[Flags]
public enum JsonOptions
{
None = 0,
WriteWhitespace = 0x00000001,
DontWriteWhitespace = 0x00000002,
StrictParser = 0x00000004,
NonStrictParser = 0x00000008,
Flush = 0x00000010,
AutoSavePreviousVersion = 0x00000020, // Use "SavePreviousVersions" static property
SavePreviousVersion = 0x00000040, // Always save previous version
}
// API
public static class Json
{
static Json()
{
WriteWhitespaceDefault = true;
StrictParserDefault = false;
#if !PETAJSON_NO_EMIT
Json.SetFormatterResolver(Internal.Emit.MakeFormatter);
Json.SetParserResolver(Internal.Emit.MakeParser);
Json.SetIntoParserResolver(Internal.Emit.MakeIntoParser);
#endif
}
// Pretty format default
public static bool WriteWhitespaceDefault
{
get;
set;
}
// Strict parser
public static bool StrictParserDefault
{
get;
set;
}
// Write an object to a text writer
public static void Write(TextWriter w, object o, JsonOptions options = JsonOptions.None)
{
var writer = new Internal.Writer(w, ResolveOptions(options));
writer.WriteValue(o);
}
static void DeleteFile(string filename)
{
try
{
System.IO.File.Delete(filename);
}
catch
{
// Don't care
}
}
public static bool SavePreviousVersions
{
get;
set;
}
// Write a file atomically by writing to a temp file and then renaming it - prevents corrupted files if crash
// in middle of writing file.
public static void WriteFileAtomic(string filename, object o, JsonOptions options = JsonOptions.None, string backupFilename = null)
{
var tempName = filename + ".tmp";
try
{
// Write the temp file
WriteFile(tempName, o, (options | JsonOptions.Flush));
if (System.IO.File.Exists(filename))
{
bool savePreviousVersion = false;
if ((options & JsonOptions.AutoSavePreviousVersion) != 0)
{
savePreviousVersion = SavePreviousVersions;
}
else if ((options & JsonOptions.SavePreviousVersion) != 0)
{
savePreviousVersion = true;
}
// Work out backup filename
if (savePreviousVersion)
{
// Make sure have a backup filename
if (backupFilename == null)
{
backupFilename = filename + ".previous";
}
}
else
{
// No backup
backupFilename = null;
}
// Replace it
int retry = 0;
while (true)
{
try
{
File.Replace(tempName, filename, backupFilename);
break;
}
catch (System.IO.IOException x)
{
retry++;
if (retry >= 5)
{
throw new System.IO.IOException(string.Format("Failed to replace temp file {0} with {1} and backup {2}, reason {3}", tempName, filename, backupFilename, x.Message), x);
}
System.Threading.Thread.Sleep(2000);
}
}
}
else
{
// Rename it
File.Move(tempName, filename);
}
}
catch
{
DeleteFile(tempName);
throw;
}
}
// Write an object to a file
public static void WriteFile(string filename, object o, JsonOptions options = JsonOptions.None)
{
using (var w = new StreamWriter(filename))
{
Write(w, o, options);
if ((options & JsonOptions.Flush) != 0)
{
w.Flush();
w.BaseStream.Flush();
}
}
}
// Format an object as a json string
public static string Format(object o, JsonOptions options = JsonOptions.None)
{
var sw = new StringWriter();
var writer = new Internal.Writer(sw, ResolveOptions(options));
writer.WriteValue(o);
return sw.ToString();
}
// Parse an object of specified type from a text reader
public static object Parse(TextReader r, Type type, JsonOptions options = JsonOptions.None)
{
Internal.Reader reader = null;
try
{
reader = new Internal.Reader(r, ResolveOptions(options));
var retv = reader.Parse(type);
reader.CheckEOF();
return retv;
}
catch (Exception x)
{
var loc = reader == null ? new JsonLineOffset() : reader.CurrentTokenPosition;
Console.WriteLine("Exception thrown while parsing JSON at {0}, context:{1}\n{2}", loc, reader.Context, x.ToString());
throw new JsonParseException(x, reader.Context, loc);
}
}
// Parse an object of specified type from a text reader
public static T Parse<T>(TextReader r, JsonOptions options = JsonOptions.None)
{
return (T)Parse(r, typeof(T), options);
}
// Parse from text reader into an already instantied object
public static void ParseInto(TextReader r, Object into, JsonOptions options = JsonOptions.None)
{
if (into == null)
throw new NullReferenceException();
if (into.GetType().IsValueType)
throw new InvalidOperationException("Can't ParseInto a value type");
Internal.Reader reader = null;
try
{
reader = new Internal.Reader(r, ResolveOptions(options));
reader.ParseInto(into);
reader.CheckEOF();
}
catch (Exception x)
{
var loc = reader == null ? new JsonLineOffset() : reader.CurrentTokenPosition;
Console.WriteLine("Exception thrown while parsing JSON at {0}, context:{1}\n{2}", loc, reader.Context, x.ToString());
throw new JsonParseException(x, reader.Context, loc);
}
}
// Parse an object of specified type from a file
public static object ParseFile(string filename, Type type, JsonOptions options = JsonOptions.None)
{
using (var r = new StreamReader(filename))
{
return Parse(r, type, options);
}
}
// Parse an object of specified type from a file
public static T ParseFile<T>(string filename, JsonOptions options = JsonOptions.None)
{
using (var r = new StreamReader(filename))
{
return Parse<T>(r, options);
}
}
// Parse from file into an already instantied object
public static void ParseFileInto(string filename, Object into, JsonOptions options = JsonOptions.None)
{
using (var r = new StreamReader(filename))
{
ParseInto(r, into, options);
}
}
// Parse an object from a string
public static object Parse(string data, Type type, JsonOptions options = JsonOptions.None)
{
return Parse(new StringReader(data), type, options);
}
// Parse an object from a string
public static T Parse<T>(string data, JsonOptions options = JsonOptions.None)
{
return (T)Parse<T>(new StringReader(data), options);
}
// Parse from string into an already instantiated object
public static void ParseInto(string data, Object into, JsonOptions options = JsonOptions.None)
{
ParseInto(new StringReader(data), into, options);
}
// Create a clone of an object
public static T Clone<T>(T source)
{
return (T)Reparse(source.GetType(), source);
}
// Create a clone of an object (untyped)
public static object Clone(object source)
{
return Reparse(source.GetType(), source);
}
// Clone an object into another instance
public static void CloneInto(object dest, object source)
{
ReparseInto(dest, source);
}
// Reparse an object by writing to a stream and re-reading (possibly
// as a different type).
public static object Reparse(Type type, object source)
{
if (source == null)
return null;
var ms = new MemoryStream();
try
{
// Write
var w = new StreamWriter(ms);
Json.Write(w, source);
w.Flush();
// Read
ms.Seek(0, SeekOrigin.Begin);
var r = new StreamReader(ms);
return Json.Parse(r, type);
}
finally
{
ms.Dispose();
}
}
// Typed version of above
public static T Reparse<T>(object source)
{
return (T)Reparse(typeof(T), source);
}
// Reparse one object into another object
public static void ReparseInto(object dest, object source)
{
var ms = new MemoryStream();
try
{
// Write
var w = new StreamWriter(ms);
Json.Write(w, source);
w.Flush();
// Read
ms.Seek(0, SeekOrigin.Begin);
var r = new StreamReader(ms);
Json.ParseInto(r, dest);
}
finally
{
ms.Dispose();
}
}
// Register a callback that can format a value of a particular type into json
public static void RegisterFormatter(Type type, Action<IJsonWriter, object> formatter)
{
Internal.Writer._formatters[type] = formatter;
}
// Typed version of above
public static void RegisterFormatter<T>(Action<IJsonWriter, T> formatter)
{
RegisterFormatter(typeof(T), (w, o) => formatter(w, (T)o));
}
// Register a parser for a specified type
public static void RegisterParser(Type type, Func<IJsonReader, Type, object> parser)
{
Internal.Reader._parsers.Set(type, parser);
}
// Register a typed parser
public static void RegisterParser<T>(Func<IJsonReader, Type, T> parser)
{
RegisterParser(typeof(T), (r, t) => parser(r, t));
}
// Simpler version for simple types
public static void RegisterParser(Type type, Func<object, object> parser)
{
RegisterParser(type, (r, t) => r.ReadLiteral(parser));
}
// Simpler and typesafe parser for simple types
public static void RegisterParser<T>(Func<object, T> parser)
{
RegisterParser(typeof(T), literal => parser(literal));
}
// Register an into parser
public static void RegisterIntoParser(Type type, Action<IJsonReader, object> parser)
{
Internal.Reader._intoParsers.Set(type, parser);
}
// Register an into parser
public static void RegisterIntoParser<T>(Action<IJsonReader, object> parser)
{
RegisterIntoParser(typeof(T), parser);
}
// Register a factory for instantiating objects (typically abstract classes)
// Callback will be invoked for each key in the dictionary until it returns an object
// instance and which point it will switch to serialization using reflection
public static void RegisterTypeFactory(Type type, Func<IJsonReader, string, object> factory)
{
Internal.Reader._typeFactories.Set(type, factory);
}
// Register a callback to provide a formatter for a newly encountered type
public static void SetFormatterResolver(Func<Type, Action<IJsonWriter, object>> resolver)
{
Internal.Writer._formatterResolver = resolver;
}
// Register a callback to provide a parser for a newly encountered value type
public static void SetParserResolver(Func<Type, Func<IJsonReader, Type, object>> resolver)
{
Internal.Reader._parserResolver = resolver;
}
// Register a callback to provide a parser for a newly encountered reference type
public static void SetIntoParserResolver(Func<Type, Action<IJsonReader, object>> resolver)
{
Internal.Reader._intoParserResolver = resolver;
}
public static bool WalkPath(this IDictionary<string, object> This, string Path, bool create, Func<IDictionary<string, object>, string, bool> leafCallback)
{
// Walk the path
var parts = Path.Split('.');
for (int i = 0; i < parts.Length - 1; i++)
{
object val;
if (!This.TryGetValue(parts[i], out val))
{
if (!create)
return false;
val = new Dictionary<string, object>();
This[parts[i]] = val;
}
This = (IDictionary<string, object>)val;
}
// Process the leaf
return leafCallback(This, parts[parts.Length - 1]);
}
public static bool PathExists(this IDictionary<string, object> This, string Path)
{
return This.WalkPath(Path, false, (dict, key) => dict.ContainsKey(key));
}
public static object GetPath(this IDictionary<string, object> This, Type type, string Path, object def)
{
This.WalkPath(Path, false, (dict, key) =>
{
object val;
if (dict.TryGetValue(key, out val))
{
if (val == null)
def = val;
else if (type.IsAssignableFrom(val.GetType()))
def = val;
else
def = Reparse(type, val);
}
return true;
});
return def;
}
// Ensure there's an object of type T at specified path
public static T GetObjectAtPath<T>(this IDictionary<string, object> This, string Path) where T : class, new()
{
T retVal = null;
This.WalkPath(Path, true, (dict, key) =>
{
object val;
dict.TryGetValue(key, out val);
retVal = val as T;
if (retVal == null)
{
retVal = val == null ? new T() : Reparse<T>(val);
dict[key] = retVal;
}
return true;
});
return retVal;
}
public static T GetPath<T>(this IDictionary<string, object> This, string Path, T def = default(T))
{
return (T)This.GetPath(typeof(T), Path, def);
}
public static void SetPath(this IDictionary<string, object> This, string Path, object value)
{
This.WalkPath(Path, true, (dict, key) => { dict[key] = value; return true; });
}
// Resolve passed options
static JsonOptions ResolveOptions(JsonOptions options)
{
JsonOptions resolved = JsonOptions.None;
if ((options & (JsonOptions.WriteWhitespace | JsonOptions.DontWriteWhitespace)) != 0)
resolved |= options & (JsonOptions.WriteWhitespace | JsonOptions.DontWriteWhitespace);
else
resolved |= WriteWhitespaceDefault ? JsonOptions.WriteWhitespace : JsonOptions.DontWriteWhitespace;
if ((options & (JsonOptions.StrictParser | JsonOptions.NonStrictParser)) != 0)
resolved |= options & (JsonOptions.StrictParser | JsonOptions.NonStrictParser);
else
resolved |= StrictParserDefault ? JsonOptions.StrictParser : JsonOptions.NonStrictParser;
return resolved;
}
}
// Called before loading via reflection
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public interface IJsonLoading
{
void OnJsonLoading(IJsonReader r);
}
// Called after loading via reflection
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public interface IJsonLoaded
{
void OnJsonLoaded(IJsonReader r);
}
// Called for each field while loading from reflection
// Return true if handled
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public interface IJsonLoadField
{
bool OnJsonField(IJsonReader r, string key);
}
// Called when about to write using reflection
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public interface IJsonWriting
{
void OnJsonWriting(IJsonWriter w);
}
// Called after written using reflection
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public interface IJsonWritten
{
void OnJsonWritten(IJsonWriter w);
}
// Describes the current literal in the json stream
public enum LiteralKind
{
None,
String,
Null,
True,
False,
SignedInteger,
UnsignedInteger,
FloatingPoint,
}
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public enum Token
{
EOF,
Identifier,
Literal,
OpenBrace,
CloseBrace,
OpenSquare,
CloseSquare,
Equal,
Colon,
SemiColon,
Comma,
}
// Passed to registered parsers
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public interface IJsonReader
{
object Parse(Type type);
T Parse<T>();
void ParseInto(object into);
Token CurrentToken { get; }
object ReadLiteral(Func<object, object> converter);
void ParseDictionary(Action<string> callback);
void ParseArray(Action callback);
LiteralKind GetLiteralKind();
string GetLiteralString();
void NextToken();
}
// Passed to registered formatters
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public interface IJsonWriter
{
void WriteStringLiteral(string str);
void WriteRaw(string str);
void WriteArray(Action callback);
void WriteDictionary(Action callback);
void WriteValue(object value);
void WriteElement();
void WriteKey(string key);
void WriteKeyNoEscaping(string key);
}
// Exception thrown for any parse error
public class JsonParseException : Exception
{
public JsonParseException(Exception inner, string context, JsonLineOffset position) :
base(string.Format("JSON parse error at {0}{1} - {2}", position, string.IsNullOrEmpty(context) ? "" : string.Format(", context {0}", context), inner.Message), inner)
{
Position = position;
Context = context;
}
public JsonLineOffset Position;
public string Context;
}
// Represents a line and character offset position in the source Json
public struct JsonLineOffset
{
public int Line;
public int Offset;
public override string ToString()
{
return string.Format("line {0}, character {1}", Line + 1, Offset + 1);
}
}
// Used to decorate fields and properties that should be serialized
//
// - [Json] on class or struct causes all public fields and properties to be serialized
// - [Json] on a public or non-public field or property causes that member to be serialized
// - [JsonExclude] on a field or property causes that field to be not serialized
// - A class or struct with no [Json] attribute has all public fields/properties serialized
// - A class or struct with no [Json] attribute but a [Json] attribute on one or more members only serializes those members
//
// Use [Json("keyname")] to explicitly specify the key to be used
// [Json] without the keyname will be serialized using the name of the member with the first letter lowercased.
//
// [Json(KeepInstance=true)] causes container/subobject types to be serialized into the existing member instance (if not null)
//
// You can also use the system supplied DataContract and DataMember attributes. They'll only be used if there
// are no PetaJson attributes on the class or it's members. You must specify DataContract on the type and
// DataMember on any fields/properties that require serialization. There's no need for exclude attribute.
// When using DataMember, the name of the field or property is used as is - the first letter is left in upper case
//
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field)]
public class JsonAttribute : Attribute
{
public JsonAttribute()
{
_key = null;
}
public JsonAttribute(string key)
{
_key = key;
}
// Key used to save this field/property
string _key;
public string Key
{
get { return _key; }
}
// If true uses ParseInto to parse into the existing object instance
// If false, creates a new instance as assigns it to the property
public bool KeepInstance
{
get;
set;
}
// If true, the property will be loaded, but not saved
// Use to upgrade deprecated persisted settings, but not
// write them back out again
public bool Deprecated
{
get;
set;
}
}
// See comments for JsonAttribute above
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class JsonExcludeAttribute : Attribute
{
public JsonExcludeAttribute()
{
}
}
// Apply to enum values to specify which enum value to select
// if the supplied json value doesn't match any.
// If not found throws an exception
// eg, any unknown values in the json will be mapped to Fruit.unknown
//
// [JsonUnknown(Fruit.unknown)]
// enum Fruit
// {
// unknown,
// Apple,
// Pear,
// }
[AttributeUsage(AttributeTargets.Enum)]
public class JsonUnknownAttribute : Attribute
{
public JsonUnknownAttribute(object unknownValue)
{
UnknownValue = unknownValue;
}
public object UnknownValue
{
get;
private set;
}
}
namespace Internal
{
class CancelReaderException : Exception
{ }
// Helper to create instances but include the type name in the thrown exception
public static class DecoratingActivator
{
public static object CreateInstance(Type t)
{
try
{
return Activator.CreateInstance(t);
}
catch (Exception x)
{
throw new InvalidOperationException(string.Format("Failed to create instance of type '{0}'", t.FullName), x);
}
}
}
public class Reader : IJsonReader
{
static Reader()
{
// Setup default resolvers
_parserResolver = ResolveParser;
_intoParserResolver = ResolveIntoParser;
Func<IJsonReader, Type, object> simpleConverter = (reader, type) =>
{
return reader.ReadLiteral(literal => Convert.ChangeType(literal, type, CultureInfo.InvariantCulture));
};
Func<IJsonReader, Type, object> numberConverter = (reader, type) =>
{
switch (reader.GetLiteralKind())
{
case LiteralKind.SignedInteger:
case LiteralKind.UnsignedInteger:
{
var str = reader.GetLiteralString();
if (str.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase))
{
var tempValue = Convert.ToUInt64(str.Substring(2), 16);
object val = Convert.ChangeType(tempValue, type, CultureInfo.InvariantCulture);
reader.NextToken();
return val;
}
else
{
object val = Convert.ChangeType(str, type, CultureInfo.InvariantCulture);
reader.NextToken();
return val;
}
}
case LiteralKind.FloatingPoint:
{
object val = Convert.ChangeType(reader.GetLiteralString(), type, CultureInfo.InvariantCulture);
reader.NextToken();
return val;
}
}
throw new InvalidDataException("expected a numeric literal");
};
// Default type handlers
_parsers.Set(typeof(string), simpleConverter);
_parsers.Set(typeof(char), simpleConverter);
_parsers.Set(typeof(bool), simpleConverter);
_parsers.Set(typeof(byte), numberConverter);
_parsers.Set(typeof(sbyte), numberConverter);
_parsers.Set(typeof(short), numberConverter);
_parsers.Set(typeof(ushort), numberConverter);
_parsers.Set(typeof(int), numberConverter);
_parsers.Set(typeof(uint), numberConverter);
_parsers.Set(typeof(long), numberConverter);
_parsers.Set(typeof(ulong), numberConverter);
_parsers.Set(typeof(decimal), numberConverter);
_parsers.Set(typeof(float), numberConverter);
_parsers.Set(typeof(double), numberConverter);
_parsers.Set(typeof(DateTime), (reader, type) =>
{
return reader.ReadLiteral(literal => Utils.FromUnixMilliseconds((long)Convert.ChangeType(literal, typeof(long), CultureInfo.InvariantCulture)));
});
_parsers.Set(typeof(byte[]), (reader, type) =>
{
if (reader.CurrentToken == Token.OpenSquare)
throw new CancelReaderException();
return reader.ReadLiteral(literal => Convert.FromBase64String((string)Convert.ChangeType(literal, typeof(string), CultureInfo.InvariantCulture)));
});
}
public Reader(TextReader r, JsonOptions options)
{
_tokenizer = new Tokenizer(r, options);
_options = options;
}
Tokenizer _tokenizer;
JsonOptions _options;
List<string> _contextStack = new List<string>();
public string Context
{
get
{
return string.Join(".", _contextStack);
}
}
static Action<IJsonReader, object> ResolveIntoParser(Type type)
{
var ri = ReflectionInfo.GetReflectionInfo(type);
if (ri != null)
return ri.ParseInto;
else
return null;
}
static Func<IJsonReader, Type, object> ResolveParser(Type type)
{
// See if the Type has a static parser method - T ParseJson(IJsonReader)
var parseJson = ReflectionInfo.FindParseJson(type);
if (parseJson != null)
{
if (parseJson.GetParameters()[0].ParameterType == typeof(IJsonReader))
{
return (r, t) => parseJson.Invoke(null, new Object[] { r });
}
else
{
return (r, t) =>
{
if (r.GetLiteralKind() == LiteralKind.String)
{
var o = parseJson.Invoke(null, new Object[] { r.GetLiteralString() });
r.NextToken();
return o;
}
throw new InvalidDataException(string.Format("Expected string literal for type {0}", type.FullName));
};
}
}
return (r, t) =>
{
var into = DecoratingActivator.CreateInstance(type);
r.ParseInto(into);
return into;
};
}
public JsonLineOffset CurrentTokenPosition
{
get { return _tokenizer.CurrentTokenPosition; }
}
public Token CurrentToken
{
get { return _tokenizer.CurrentToken; }
}
// ReadLiteral is implemented with a converter callback so that any
// errors on converting to the target type are thrown before the tokenizer
// is advanced to the next token. This ensures error location is reported
// at the start of the literal, not the following token.
public object ReadLiteral(Func<object, object> converter)
{
_tokenizer.Check(Token.Literal);
var retv = converter(_tokenizer.LiteralValue);
_tokenizer.NextToken();
return retv;
}
public void CheckEOF()
{
_tokenizer.Check(Token.EOF);
}
public object Parse(Type type)
{
// Null?
if (_tokenizer.CurrentToken == Token.Literal && _tokenizer.LiteralKind == LiteralKind.Null)
{
_tokenizer.NextToken();
return null;
}
// Handle nullable types
var typeUnderlying = Nullable.GetUnderlyingType(type);
if (typeUnderlying != null)
type = typeUnderlying;
// See if we have a reader
Func<IJsonReader, Type, object> parser;
if (Reader._parsers.TryGetValue(type, out parser))
{
try
{
return parser(this, type);
}
catch (CancelReaderException)
{
// Reader aborted trying to read this format
}
}
// See if we have factory
Func<IJsonReader, string, object> factory;
if (Reader._typeFactories.TryGetValue(type, out factory))
{
// Try first without passing dictionary keys
object into = factory(this, null);
if (into == null)
{
// This is a awkward situation. The factory requires a value from the dictionary
// in order to create the target object (typically an abstract class with the class
// kind recorded in the Json). Since there's no guarantee of order in a json dictionary
// we can't assume the required key is first.
// So, create a bookmark on the tokenizer, read keys until the factory returns an
// object instance and then rewind the tokenizer and continue
// Create a bookmark so we can rewind
_tokenizer.CreateBookmark();
// Skip the opening brace
_tokenizer.Skip(Token.OpenBrace);
// First pass to work out type
ParseDictionaryKeys(key =>
{
// Try to instantiate the object
into = factory(this, key);
return into == null;
});
// Move back to start of the dictionary
_tokenizer.RewindToBookmark();
// Quit if still didn't get an object from the factory
if (into == null)
throw new InvalidOperationException("Factory didn't create object instance (probably due to a missing key in the Json)");
}
// Second pass
ParseInto(into);
// Done
return into;
}
// Do we already have an into parser?
Action<IJsonReader, object> intoParser;
if (Reader._intoParsers.TryGetValue(type, out intoParser))
{
var into = DecoratingActivator.CreateInstance(type);
ParseInto(into);
return into;
}
// Enumerated type?
if (type.IsEnum)
{
if (type.GetCustomAttributes(typeof(FlagsAttribute), false).Any())
return ReadLiteral(literal => {
try
{
return Enum.Parse(type, (string)literal);
}
catch
{
return Enum.ToObject(type, literal);
}
});
else
return ReadLiteral(literal => {
try
{
return Enum.Parse(type, (string)literal);
}
catch (Exception)
{
var attr = type.GetCustomAttributes(typeof(JsonUnknownAttribute), false).FirstOrDefault();
if (attr == null)
throw;
return ((JsonUnknownAttribute)attr).UnknownValue;
}
});
}
// Array?
if (type.IsArray && type.GetArrayRank() == 1)
{
// First parse as a List<>
var listType = typeof(List<>).MakeGenericType(type.GetElementType());
var list = DecoratingActivator.CreateInstance(listType);
ParseInto(list);
return listType.GetMethod("ToArray").Invoke(list, null);
}
// IEnumerable
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
// First parse as a List<>
var declType = type.GetGenericArguments()[0];
var listType = typeof(List<>).MakeGenericType(declType);
var list = DecoratingActivator.CreateInstance(listType);
ParseInto(list);
return list;
}
// Convert interfaces to concrete types
if (type.IsInterface)
type = Utils.ResolveInterfaceToClass(type);
// Untyped dictionary?
if (_tokenizer.CurrentToken == Token.OpenBrace && (type.IsAssignableFrom(typeof(IDictionary<string, object>))))
{
#if !PETAJSON_NO_DYNAMIC
var container = (new ExpandoObject()) as IDictionary<string, object>;
#else
var container = new Dictionary<string, object>();
#endif
ParseDictionary(key =>
{
container[key] = Parse(typeof(Object));
});
return container;
}
// Untyped list?
if (_tokenizer.CurrentToken == Token.OpenSquare && (type.IsAssignableFrom(typeof(List<object>))))
{
var container = new List<object>();
ParseArray(() =>
{
container.Add(Parse(typeof(Object)));
});
return container;
}
// Untyped literal?
if (_tokenizer.CurrentToken == Token.Literal && type.IsAssignableFrom(_tokenizer.LiteralType))
{
var lit = _tokenizer.LiteralValue;
_tokenizer.NextToken();
return lit;
}
// Call value type resolver
if (type.IsValueType)
{
var tp = _parsers.Get(type, () => _parserResolver(type));
if (tp != null)
{
return tp(this, type);
}
}
// Call reference type resolver
if (type.IsClass && type != typeof(object))
{
var into = DecoratingActivator.CreateInstance(type);
ParseInto(into);
return into;
}
// Give up
throw new InvalidDataException(string.Format("syntax error, unexpected token {0}", _tokenizer.CurrentToken));
}
// Parse into an existing object instance
public void ParseInto(object into)
{
if (into == null)
return;
if (_tokenizer.CurrentToken == Token.Literal && _tokenizer.LiteralKind == LiteralKind.Null)
{
throw new InvalidOperationException("can't parse null into existing instance");
//return;
}
var type = into.GetType();
// Existing parse into handler?
Action<IJsonReader, object> parseInto;
if (_intoParsers.TryGetValue(type, out parseInto))
{
parseInto(this, into);
return;
}
// Generic dictionary?
var dictType = Utils.FindGenericInterface(type, typeof(IDictionary<,>));
if (dictType != null)
{
// Get the key and value types
var typeKey = dictType.GetGenericArguments()[0];
var typeValue = dictType.GetGenericArguments()[1];
// Parse it
IDictionary dict = (IDictionary)into;
dict.Clear();
ParseDictionary(key =>
{
dict.Add(Convert.ChangeType(key, typeKey), Parse(typeValue));
});
return;
}
// Generic list
var listType = Utils.FindGenericInterface(type, typeof(IList<>));
if (listType != null)
{
// Get element type
var typeElement = listType.GetGenericArguments()[0];
// Parse it
IList list = (IList)into;
list.Clear();
ParseArray(() =>
{
list.Add(Parse(typeElement));
});
return;
}
// Untyped dictionary
var objDict = into as IDictionary;
if (objDict != null)
{
objDict.Clear();
ParseDictionary(key =>
{
objDict[key] = Parse(typeof(Object));
});
return;
}
// Untyped list
var objList = into as IList;
if (objList != null)
{
objList.Clear();
ParseArray(() =>
{
objList.Add(Parse(typeof(Object)));
});
return;
}
// Try to resolve a parser
var intoParser = _intoParsers.Get(type, () => _intoParserResolver(type));
if (intoParser != null)
{
intoParser(this, into);
return;
}
throw new InvalidOperationException(string.Format("Don't know how to parse into type '{0}'", type.FullName));
}
public T Parse<T>()
{
return (T)Parse(typeof(T));
}
public LiteralKind GetLiteralKind()
{
return _tokenizer.LiteralKind;
}
public string GetLiteralString()
{
return _tokenizer.String;
}
public void NextToken()
{
_tokenizer.NextToken();
}
// Parse a dictionary
public void ParseDictionary(Action<string> callback)
{
_tokenizer.Skip(Token.OpenBrace);
ParseDictionaryKeys(key => { callback(key); return true; });
_tokenizer.Skip(Token.CloseBrace);
}
// Parse dictionary keys, calling callback for each one. Continues until end of input
// or when callback returns false
private void ParseDictionaryKeys(Func<string, bool> callback)
{
// End?
while (_tokenizer.CurrentToken != Token.CloseBrace)
{
// Parse the key
string key = null;
if (_tokenizer.CurrentToken == Token.Identifier && (_options & JsonOptions.StrictParser) == 0)
{
key = _tokenizer.String;
}
else if (_tokenizer.CurrentToken == Token.Literal && _tokenizer.LiteralKind == LiteralKind.String)
{
key = (string)_tokenizer.LiteralValue;
}
else
{
throw new InvalidDataException("syntax error, expected string literal or identifier");
}
_tokenizer.NextToken();
_tokenizer.Skip(Token.Colon);
// Remember current position
var pos = _tokenizer.CurrentTokenPosition;
// Call the callback, quit if cancelled
_contextStack.Add(key);
bool doDefaultProcessing = callback(key);
_contextStack.RemoveAt(_contextStack.Count - 1);
if (!doDefaultProcessing)
return;
// If the callback didn't read anything from the tokenizer, then skip it ourself
if (pos.Line == _tokenizer.CurrentTokenPosition.Line && pos.Offset == _tokenizer.CurrentTokenPosition.Offset)
{
Parse(typeof(object));
}
// Separating/trailing comma
if (_tokenizer.SkipIf(Token.Comma))
{
if ((_options & JsonOptions.StrictParser) != 0 && _tokenizer.CurrentToken == Token.CloseBrace)
{
throw new InvalidDataException("Trailing commas not allowed in strict mode");
}
continue;
}
// End
break;
}
}
// Parse an array
public void ParseArray(Action callback)
{
_tokenizer.Skip(Token.OpenSquare);
int index = 0;
while (_tokenizer.CurrentToken != Token.CloseSquare)
{
_contextStack.Add(string.Format("[{0}]", index));
callback();
_contextStack.RemoveAt(_contextStack.Count - 1);
if (_tokenizer.SkipIf(Token.Comma))
{
if ((_options & JsonOptions.StrictParser) != 0 && _tokenizer.CurrentToken == Token.CloseSquare)
{
throw new InvalidDataException("Trailing commas not allowed in strict mode");
}
continue;
}
break;
}
_tokenizer.Skip(Token.CloseSquare);
}
// Yikes!
internal static Func<Type, Action<IJsonReader, object>> _intoParserResolver;
internal static Func<Type, Func<IJsonReader, Type, object>> _parserResolver;
internal static ThreadSafeCache<Type, Func<IJsonReader, Type, object>> _parsers = new ThreadSafeCache<Type, Func<IJsonReader, Type, object>>();
internal static ThreadSafeCache<Type, Action<IJsonReader, object>> _intoParsers = new ThreadSafeCache<Type, Action<IJsonReader, object>>();
internal static ThreadSafeCache<Type, Func<IJsonReader, string, object>> _typeFactories = new ThreadSafeCache<Type, Func<IJsonReader, string, object>>();
}
public class Writer : IJsonWriter
{
static Writer()
{
_formatterResolver = ResolveFormatter;
// Register standard formatters
_formatters.Add(typeof(string), (w, o) => w.WriteStringLiteral((string)o));
_formatters.Add(typeof(char), (w, o) => w.WriteStringLiteral(((char)o).ToString()));
_formatters.Add(typeof(bool), (w, o) => w.WriteRaw(((bool)o) ? "true" : "false"));
Action<IJsonWriter, object> convertWriter = (w, o) => w.WriteRaw((string)Convert.ChangeType(o, typeof(string), System.Globalization.CultureInfo.InvariantCulture));
_formatters.Add(typeof(int), convertWriter);
_formatters.Add(typeof(uint), convertWriter);
_formatters.Add(typeof(long), convertWriter);
_formatters.Add(typeof(ulong), convertWriter);
_formatters.Add(typeof(short), convertWriter);
_formatters.Add(typeof(ushort), convertWriter);
_formatters.Add(typeof(decimal), convertWriter);
_formatters.Add(typeof(byte), convertWriter);
_formatters.Add(typeof(sbyte), convertWriter);
_formatters.Add(typeof(DateTime), (w, o) => convertWriter(w, Utils.ToUnixMilliseconds((DateTime)o)));
_formatters.Add(typeof(float), (w, o) => w.WriteRaw(((float)o).ToString("R", System.Globalization.CultureInfo.InvariantCulture)));
_formatters.Add(typeof(double), (w, o) => w.WriteRaw(((double)o).ToString("R", System.Globalization.CultureInfo.InvariantCulture)));
_formatters.Add(typeof(byte[]), (w, o) =>
{
w.WriteRaw("\"");
w.WriteRaw(Convert.ToBase64String((byte[])o));
w.WriteRaw("\"");
});
}
internal static Func<Type, Action<IJsonWriter, object>> _formatterResolver;
internal static Dictionary<Type, Action<IJsonWriter, object>> _formatters = new Dictionary<Type, Action<IJsonWriter, object>>();
static Action<IJsonWriter, object> ResolveFormatter(Type type)
{
// Try `void FormatJson(IJsonWriter)`
var formatJson = ReflectionInfo.FindFormatJson(type);
if (formatJson != null)
{
if (formatJson.ReturnType == typeof(void))
return (w, obj) => formatJson.Invoke(obj, new Object[] { w });
if (formatJson.ReturnType == typeof(string))
return (w, obj) => w.WriteStringLiteral((string)formatJson.Invoke(obj, new Object[] { }));
}
var ri = ReflectionInfo.GetReflectionInfo(type);
if (ri != null)
return ri.Write;
else
return null;
}
public Writer(TextWriter w, JsonOptions options)
{
_writer = w;
_atStartOfLine = true;
_needElementSeparator = false;
_options = options;
}
private TextWriter _writer;
private int IndentLevel;
private bool _atStartOfLine;
private bool _needElementSeparator = false;
private JsonOptions _options;
private char _currentBlockKind = '\0';
// Move to the next line
public void NextLine()
{
if (_atStartOfLine)
return;
if ((_options & JsonOptions.WriteWhitespace) != 0)
{
WriteRaw("\n");
WriteRaw(new string('\t', IndentLevel));
}
_atStartOfLine = true;
}
// Start the next element, writing separators and white space
void NextElement()
{
if (_needElementSeparator)
{
WriteRaw(",");
NextLine();
}
else
{
NextLine();
IndentLevel++;
WriteRaw(_currentBlockKind.ToString());
NextLine();
}
_needElementSeparator = true;
}
// Write next array element
public void WriteElement()
{
if (_currentBlockKind != '[')
throw new InvalidOperationException("Attempt to write array element when not in array block");
NextElement();
}
// Write next dictionary key
public void WriteKey(string key)
{
if (_currentBlockKind != '{')
throw new InvalidOperationException("Attempt to write dictionary element when not in dictionary block");
NextElement();
WriteStringLiteral(key);
WriteRaw(((_options & JsonOptions.WriteWhitespace) != 0) ? ": " : ":");
}
// Write an already escaped dictionary key
public void WriteKeyNoEscaping(string key)
{
if (_currentBlockKind != '{')
throw new InvalidOperationException("Attempt to write dictionary element when not in dictionary block");
NextElement();
WriteRaw("\"");
WriteRaw(key);
WriteRaw("\"");
WriteRaw(((_options & JsonOptions.WriteWhitespace) != 0) ? ": " : ":");
}
// Write anything
public void WriteRaw(string str)
{
_atStartOfLine = false;
_writer.Write(str);
}
static int IndexOfEscapeableChar(string str, int pos)
{
int length = str.Length;
while (pos < length)
{
var ch = str[pos];
if (ch == '\\' || ch == '/' || ch == '\"' || (ch >= 0 && ch <= 0x1f) || (ch >= 0x7f && ch <= 0x9f) || ch == 0x2028 || ch == 0x2029)
return pos;
pos++;
}
return -1;
}
public void WriteStringLiteral(string str)
{
_atStartOfLine = false;
if (str == null)
{
_writer.Write("null");
return;
}
_writer.Write("\"");
int pos = 0;
int escapePos;
while ((escapePos = IndexOfEscapeableChar(str, pos)) >= 0)
{
if (escapePos > pos)
_writer.Write(str.Substring(pos, escapePos - pos));
switch (str[escapePos])
{
case '\"': _writer.Write("\\\""); break;
case '\\': _writer.Write("\\\\"); break;
case '/': _writer.Write("\\/"); break;
case '\b': _writer.Write("\\b"); break;
case '\f': _writer.Write("\\f"); break;
case '\n': _writer.Write("\\n"); break;
case '\r': _writer.Write("\\r"); break;
case '\t': _writer.Write("\\t"); break;
default:
_writer.Write(string.Format("\\u{0:x4}", (int)str[escapePos]));
break;
}
pos = escapePos + 1;
}
if (str.Length > pos)
_writer.Write(str.Substring(pos));
_writer.Write("\"");
}
// Write an array or dictionary block
private void WriteBlock(string open, string close, Action callback)
{
var prevBlockKind = _currentBlockKind;
_currentBlockKind = open[0];
var didNeedElementSeparator = _needElementSeparator;
_needElementSeparator = false;
callback();
if (_needElementSeparator)
{
IndentLevel--;
NextLine();
}
else
{
WriteRaw(open);
}
WriteRaw(close);
_needElementSeparator = didNeedElementSeparator;
_currentBlockKind = prevBlockKind;
}
// Write an array
public void WriteArray(Action callback)
{
WriteBlock("[", "]", callback);
}
// Write a dictionary
public void WriteDictionary(Action callback)
{
WriteBlock("{", "}", callback);
}
// Write any value
public void WriteValue(object value)
{
_atStartOfLine = false;
// Special handling for null
if (value == null)
{
_writer.Write("null");
return;
}
var type = value.GetType();
// Handle nullable types
var typeUnderlying = Nullable.GetUnderlyingType(type);
if (typeUnderlying != null)
type = typeUnderlying;
// Look up type writer
Action<IJsonWriter, object> typeWriter;
if (_formatters.TryGetValue(type, out typeWriter))
{
// Write it
typeWriter(this, value);
return;
}
// Enumerated type?
if (type.IsEnum)
{
if (type.GetCustomAttributes(typeof(FlagsAttribute), false).Any())
WriteRaw(Convert.ToUInt32(value).ToString(CultureInfo.InvariantCulture));
else
WriteStringLiteral(value.ToString());
return;
}
// Dictionary?
var d = value as System.Collections.IDictionary;
if (d != null)
{
WriteDictionary(() =>
{
foreach (var key in d.Keys)
{
WriteKey(key.ToString());
WriteValue(d[key]);
}
});
return;
}
// Dictionary?
var dso = value as IDictionary<string, object>;
if (dso != null)
{
WriteDictionary(() =>
{
foreach (var key in dso.Keys)
{
WriteKey(key.ToString());
WriteValue(dso[key]);
}
});
return;
}
// Array?
var e = value as System.Collections.IEnumerable;
if (e != null)
{
WriteArray(() =>
{
foreach (var i in e)
{
WriteElement();
WriteValue(i);
}
});
return;
}
// Resolve a formatter
var formatter = _formatterResolver(type);
if (formatter != null)
{
_formatters[type] = formatter;
formatter(this, value);
return;
}
// Give up
throw new InvalidDataException(string.Format("Don't know how to write '{0}' to json", value.GetType()));
}
}
// Information about a field or property found through reflection
public class JsonMemberInfo
{
// The Json key for this member
public string JsonKey;
// True if should keep existing instance (reference types only)
public bool KeepInstance;
// True if deprecated
public bool Deprecated;
// Reflected member info
MemberInfo _mi;
public MemberInfo Member
{
get { return _mi; }
set
{
// Store it
_mi = value;
// Also create getters and setters
if (_mi is PropertyInfo)
{
GetValue = (obj) => ((PropertyInfo)_mi).GetValue(obj, null);
SetValue = (obj, val) => ((PropertyInfo)_mi).SetValue(obj, val, null);
}
else
{
GetValue = ((FieldInfo)_mi).GetValue;
SetValue = ((FieldInfo)_mi).SetValue;
}
}
}
// Member type
public Type MemberType
{
get
{
if (Member is PropertyInfo)
{
return ((PropertyInfo)Member).PropertyType;
}
else
{
return ((FieldInfo)Member).FieldType;
}
}
}
// Get/set helpers
public Action<object, object> SetValue;
public Func<object, object> GetValue;
}
// Stores reflection info about a type
public class ReflectionInfo
{
// List of members to be serialized
public List<JsonMemberInfo> Members;
// Cache of these ReflectionInfos's
static ThreadSafeCache<Type, ReflectionInfo> _cache = new ThreadSafeCache<Type, ReflectionInfo>();
public static MethodInfo FindFormatJson(Type type)
{
if (type.IsValueType)
{
// Try `void FormatJson(IJsonWriter)`
var formatJson = type.GetMethod("FormatJson", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(IJsonWriter) }, null);
if (formatJson != null && formatJson.ReturnType == typeof(void))
return formatJson;
// Try `string FormatJson()`
formatJson = type.GetMethod("FormatJson", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { }, null);
if (formatJson != null && formatJson.ReturnType == typeof(string))
return formatJson;
}
return null;
}
public static MethodInfo FindParseJson(Type type)
{
// Try `T ParseJson(IJsonReader)`
var parseJson = type.GetMethod("ParseJson", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(IJsonReader) }, null);
if (parseJson != null && parseJson.ReturnType == type)
return parseJson;
// Try `T ParseJson(string)`
parseJson = type.GetMethod("ParseJson", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(string) }, null);
if (parseJson != null && parseJson.ReturnType == type)
return parseJson;
return null;
}
// Write one of these types
public void Write(IJsonWriter w, object val)
{
w.WriteDictionary(() =>
{
var writing = val as IJsonWriting;
if (writing != null)
writing.OnJsonWriting(w);
foreach (var jmi in Members.Where(x => !x.Deprecated))
{
w.WriteKeyNoEscaping(jmi.JsonKey);
w.WriteValue(jmi.GetValue(val));
}
var written = val as IJsonWritten;
if (written != null)
written.OnJsonWritten(w);
});
}
// Read one of these types.
// NB: Although PetaJson.JsonParseInto only works on reference type, when using reflection
// it also works for value types so we use the one method for both
public void ParseInto(IJsonReader r, object into)
{
var loading = into as IJsonLoading;
if (loading != null)
loading.OnJsonLoading(r);
r.ParseDictionary(key =>
{
ParseFieldOrProperty(r, into, key);
});
var loaded = into as IJsonLoaded;
if (loaded != null)
loaded.OnJsonLoaded(r);
}
// The member info is stored in a list (as opposed to a dictionary) so that
// the json is written in the same order as the fields/properties are defined
// On loading, we assume the fields will be in the same order, but need to
// handle if they're not. This function performs a linear search, but
// starts after the last found item as an optimization that should work
// most of the time.
int _lastFoundIndex = 0;
bool FindMemberInfo(string name, out JsonMemberInfo found)
{
for (int i = 0; i < Members.Count; i++)
{
int index = (i + _lastFoundIndex) % Members.Count;
var jmi = Members[index];
if (jmi.JsonKey == name)
{
_lastFoundIndex = index;
found = jmi;
return true;
}
}
found = null;
return false;
}
// Parse a value from IJsonReader into an object instance
public void ParseFieldOrProperty(IJsonReader r, object into, string key)
{
// IJsonLoadField
var lf = into as IJsonLoadField;
if (lf != null && lf.OnJsonField(r, key))
return;
// Find member
JsonMemberInfo jmi;
if (FindMemberInfo(key, out jmi))
{
// Try to keep existing instance
if (jmi.KeepInstance)
{
var subInto = jmi.GetValue(into);
if (subInto != null)
{
r.ParseInto(subInto);
return;
}
}
// Parse and set
var val = r.Parse(jmi.MemberType);
jmi.SetValue(into, val);
return;
}
}
// Get the reflection info for a specified type
public static ReflectionInfo GetReflectionInfo(Type type)
{
// Check cache
return _cache.Get(type, () =>
{
var allMembers = Utils.GetAllFieldsAndProperties(type);
// Does type have a [Json] attribute
bool typeMarked = type.GetCustomAttributes(typeof(JsonAttribute), true).OfType<JsonAttribute>().Any();
// Do any members have a [Json] attribute
bool anyFieldsMarked = allMembers.Any(x => x.GetCustomAttributes(typeof(JsonAttribute), false).OfType<JsonAttribute>().Any());
#if !PETAJSON_NO_DATACONTRACT
// Try with DataContract and friends
if (!typeMarked && !anyFieldsMarked && type.GetCustomAttributes(typeof(DataContractAttribute), true).OfType<DataContractAttribute>().Any())
{
var ri = CreateReflectionInfo(type, mi =>
{
// Get attributes
var attr = mi.GetCustomAttributes(typeof(DataMemberAttribute), false).OfType<DataMemberAttribute>().FirstOrDefault();
if (attr != null)
{
return new JsonMemberInfo()
{
Member = mi,
JsonKey = attr.Name ?? mi.Name, // No lower case first letter if using DataContract/Member
};
}
return null;
});
ri.Members.Sort((a, b) => String.CompareOrdinal(a.JsonKey, b.JsonKey)); // Match DataContractJsonSerializer
return ri;
}
#endif
{
// Should we serialize all public methods?
bool serializeAllPublics = typeMarked || !anyFieldsMarked;
// Build
var ri = CreateReflectionInfo(type, mi =>
{
// Explicitly excluded?
if (mi.GetCustomAttributes(typeof(JsonExcludeAttribute), false).Any())
return null;
// Get attributes
var attr = mi.GetCustomAttributes(typeof(JsonAttribute), false).OfType<JsonAttribute>().FirstOrDefault();
if (attr != null)
{
return new JsonMemberInfo()
{
Member = mi,
JsonKey = attr.Key ?? mi.Name.Substring(0, 1).ToLower() + mi.Name.Substring(1),
KeepInstance = attr.KeepInstance,
Deprecated = attr.Deprecated,
};
}
// Serialize all publics?
if (serializeAllPublics && Utils.IsPublic(mi))
{
return new JsonMemberInfo()
{
Member = mi,
JsonKey = mi.Name.Substring(0, 1).ToLower() + mi.Name.Substring(1),
};
}
return null;
});
return ri;
}
});
}
public static ReflectionInfo CreateReflectionInfo(Type type, Func<MemberInfo, JsonMemberInfo> callback)
{
// Work out properties and fields
var members = Utils.GetAllFieldsAndProperties(type).Select(x => callback(x)).Where(x => x != null).ToList();
// Anything with KeepInstance must be a reference type
var invalid = members.FirstOrDefault(x => x.KeepInstance && x.MemberType.IsValueType);
if (invalid != null)
{
throw new InvalidOperationException(string.Format("KeepInstance=true can only be applied to reference types ({0}.{1})", type.FullName, invalid.Member));
}
// Must have some members
if (!members.Any() && !Attribute.IsDefined(type, typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false))
return null;
// Create reflection info
return new ReflectionInfo() { Members = members };
}
}
public class ThreadSafeCache<TKey, TValue>
{
public ThreadSafeCache()
{
}
public TValue Get(TKey key, Func<TValue> createIt)
{
// Check if already exists
_lock.EnterReadLock();
try
{
TValue val;
if (_map.TryGetValue(key, out val))
return val;
}
finally
{
_lock.ExitReadLock();
}
// Nope, take lock and try again
_lock.EnterWriteLock();
try
{
// Check again before creating it
TValue val;
if (!_map.TryGetValue(key, out val))
{
// Store the new one
val = createIt();
_map[key] = val;
}
return val;
}
finally
{
_lock.ExitWriteLock();
}
}
public bool TryGetValue(TKey key, out TValue val)
{
_lock.EnterReadLock();
try
{
return _map.TryGetValue(key, out val);
}
finally
{
_lock.ExitReadLock();
}
}
public void Set(TKey key, TValue value)
{
_lock.EnterWriteLock();
try
{
_map[key] = value;
}
finally
{
_lock.ExitWriteLock();
}
}
Dictionary<TKey, TValue> _map = new Dictionary<TKey, TValue>();
ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
}
internal static class Utils
{
// Get all fields and properties of a type
public static IEnumerable<MemberInfo> GetAllFieldsAndProperties(Type t)
{
if (t == null)
return Enumerable.Empty<FieldInfo>();
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
return t.GetMembers(flags).Where(x => x is FieldInfo || x is PropertyInfo).Concat(GetAllFieldsAndProperties(t.BaseType));
}
public static Type FindGenericInterface(Type type, Type tItf)
{
foreach (var t in type.GetInterfaces())
{
// Is this a generic list?
if (t.IsGenericType && t.GetGenericTypeDefinition() == tItf)
return t;
}
return null;
}
public static bool IsPublic(MemberInfo mi)
{
// Public field
var fi = mi as FieldInfo;
if (fi != null)
return fi.IsPublic;
// Public property
// (We only check the get method so we can work with anonymous types)
var pi = mi as PropertyInfo;
if (pi != null)
{
var gm = pi.GetGetMethod(true);
return (gm != null && gm.IsPublic);
}
return false;
}
public static Type ResolveInterfaceToClass(Type tItf)
{
// Generic type
if (tItf.IsGenericType)
{
var genDef = tItf.GetGenericTypeDefinition();
// IList<> -> List<>
if (genDef == typeof(IList<>))
{
return typeof(List<>).MakeGenericType(tItf.GetGenericArguments());
}
// IDictionary<string,> -> Dictionary<string,>
if (genDef == typeof(IDictionary<,>) && tItf.GetGenericArguments()[0] == typeof(string))
{
return typeof(Dictionary<,>).MakeGenericType(tItf.GetGenericArguments());
}
}
// IEnumerable -> List<object>
if (tItf == typeof(IEnumerable))
return typeof(List<object>);
// IDicitonary -> Dictionary<string,object>
if (tItf == typeof(IDictionary))
return typeof(Dictionary<string, object>);
return tItf;
}
public static long ToUnixMilliseconds(DateTime This)
{
return (long)This.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;
}
public static DateTime FromUnixMilliseconds(long timeStamp)
{
return new DateTime(1970, 1, 1).AddMilliseconds(timeStamp);
}
}
public class Tokenizer
{
public Tokenizer(TextReader r, JsonOptions options)
{
_underlying = r;
_options = options;
FillBuffer();
NextChar();
NextToken();
}
private JsonOptions _options;
private StringBuilder _sb = new StringBuilder();
private TextReader _underlying;
private char[] _buf = new char[4096];
private int _pos;
private int _bufUsed;
private StringBuilder _rewindBuffer;
private int _rewindBufferPos;
private JsonLineOffset _currentCharPos;
private char _currentChar;
private Stack<ReaderState> _bookmarks = new Stack<ReaderState>();
public JsonLineOffset CurrentTokenPosition;
public Token CurrentToken;
public LiteralKind LiteralKind;
public string String;
public object LiteralValue
{
get
{
if (CurrentToken != Token.Literal)
throw new InvalidOperationException("token is not a literal");
switch (LiteralKind)
{
case LiteralKind.Null: return null;
case LiteralKind.False: return false;
case LiteralKind.True: return true;
case LiteralKind.String: return String;
case LiteralKind.SignedInteger: return long.Parse(String, CultureInfo.InvariantCulture);
case LiteralKind.UnsignedInteger:
if (String.StartsWith("0x") || String.StartsWith("0X"))
return Convert.ToUInt64(String.Substring(2), 16);
else
return ulong.Parse(String, CultureInfo.InvariantCulture);
case LiteralKind.FloatingPoint: return double.Parse(String, CultureInfo.InvariantCulture);
}
return null;
}
}
public Type LiteralType
{
get
{
if (CurrentToken != Token.Literal)
throw new InvalidOperationException("token is not a literal");
switch (LiteralKind)
{
case LiteralKind.Null: return typeof(Object);
case LiteralKind.False: return typeof(Boolean);
case LiteralKind.True: return typeof(Boolean);
case LiteralKind.String: return typeof(string);
case LiteralKind.SignedInteger: return typeof(long);
case LiteralKind.UnsignedInteger: return typeof(ulong);
case LiteralKind.FloatingPoint: return typeof(double);
}
return null;
}
}
// This object represents the entire state of the reader and is used for rewind
struct ReaderState
{
public ReaderState(Tokenizer tokenizer)
{
_currentCharPos = tokenizer._currentCharPos;
_currentChar = tokenizer._currentChar;
_string = tokenizer.String;
_literalKind = tokenizer.LiteralKind;
_rewindBufferPos = tokenizer._rewindBufferPos;
_currentTokenPos = tokenizer.CurrentTokenPosition;
_currentToken = tokenizer.CurrentToken;
}
public void Apply(Tokenizer tokenizer)
{
tokenizer._currentCharPos = _currentCharPos;
tokenizer._currentChar = _currentChar;
tokenizer._rewindBufferPos = _rewindBufferPos;
tokenizer.CurrentToken = _currentToken;
tokenizer.CurrentTokenPosition = _currentTokenPos;
tokenizer.String = _string;
tokenizer.LiteralKind = _literalKind;
}
private JsonLineOffset _currentCharPos;
private JsonLineOffset _currentTokenPos;
private char _currentChar;
private Token _currentToken;
private LiteralKind _literalKind;
private string _string;
private int _rewindBufferPos;
}
// Create a rewind bookmark
public void CreateBookmark()
{
_bookmarks.Push(new ReaderState(this));
if (_rewindBuffer == null)
{
_rewindBuffer = new StringBuilder();
_rewindBufferPos = 0;
}
}
// Discard bookmark
public void DiscardBookmark()
{
_bookmarks.Pop();
if (_bookmarks.Count == 0)
{
_rewindBuffer = null;
_rewindBufferPos = 0;
}
}
// Rewind to a bookmark
public void RewindToBookmark()
{
_bookmarks.Pop().Apply(this);
}
// Fill buffer by reading from underlying TextReader
void FillBuffer()
{
_bufUsed = _underlying.Read(_buf, 0, _buf.Length);
_pos = 0;
}
// Get the next character from the input stream
// (this function could be extracted into a few different methods, but is mostly inlined
// for performance - yes it makes a difference)
public char NextChar()
{
if (_rewindBuffer == null)
{
if (_pos >= _bufUsed)
{
if (_bufUsed > 0)
{
FillBuffer();
}
if (_bufUsed == 0)
{
return _currentChar = '\0';
}
}
// Next
_currentCharPos.Offset++;
return _currentChar = _buf[_pos++];
}
if (_rewindBufferPos < _rewindBuffer.Length)
{
_currentCharPos.Offset++;
return _currentChar = _rewindBuffer[_rewindBufferPos++];
}
else
{
if (_pos >= _bufUsed && _bufUsed > 0)
FillBuffer();
_currentChar = _bufUsed == 0 ? '\0' : _buf[_pos++];
_rewindBuffer.Append(_currentChar);
_rewindBufferPos++;
_currentCharPos.Offset++;
return _currentChar;
}
}
// Read the next token from the input stream
// (Mostly inline for performance)
public void NextToken()
{
while (true)
{
// Skip whitespace and handle line numbers
while (true)
{
if (_currentChar == '\r')
{
if (NextChar() == '\n')
{
NextChar();
}
_currentCharPos.Line++;
_currentCharPos.Offset = 0;
}
else if (_currentChar == '\n')
{
if (NextChar() == '\r')
{
NextChar();
}
_currentCharPos.Line++;
_currentCharPos.Offset = 0;
}
else if (_currentChar == ' ')
{
NextChar();
}
else if (_currentChar == '\t')
{
NextChar();
}
else
break;
}
// Remember position of token
CurrentTokenPosition = _currentCharPos;
// Handle common characters first
switch (_currentChar)
{
case '/':
// Comments not support in strict mode
if ((_options & JsonOptions.StrictParser) != 0)
{
throw new InvalidDataException(string.Format("syntax error, unexpected character '{0}'", _currentChar));
}
// Process comment
NextChar();
switch (_currentChar)
{
case '/':
NextChar();
while (_currentChar != '\0' && _currentChar != '\r' && _currentChar != '\n')
{
NextChar();
}
break;
case '*':
bool endFound = false;
while (!endFound && _currentChar != '\0')
{
if (_currentChar == '*')
{
NextChar();
if (_currentChar == '/')
{
endFound = true;
}
}
NextChar();
}
break;
default:
throw new InvalidDataException("syntax error, unexpected character after slash");
}
continue;
case '\"':
case '\'':
{
_sb.Length = 0;
var quoteKind = _currentChar;
NextChar();
while (_currentChar != '\0')
{
if (_currentChar == '\\')
{
NextChar();
var escape = _currentChar;
switch (escape)
{
case '\"': _sb.Append('\"'); break;
case '\\': _sb.Append('\\'); break;
case '/': _sb.Append('/'); break;
case 'b': _sb.Append('\b'); break;
case 'f': _sb.Append('\f'); break;
case 'n': _sb.Append('\n'); break;
case 'r': _sb.Append('\r'); break;
case 't': _sb.Append('\t'); break;
case 'u':
var sbHex = new StringBuilder();
for (int i = 0; i < 4; i++)
{
NextChar();
sbHex.Append(_currentChar);
}
_sb.Append((char)Convert.ToUInt16(sbHex.ToString(), 16));
break;
default:
throw new InvalidDataException(string.Format("Invalid escape sequence in string literal: '\\{0}'", _currentChar));
}
}
else if (_currentChar == quoteKind)
{
String = _sb.ToString();
CurrentToken = Token.Literal;
LiteralKind = LiteralKind.String;
NextChar();
return;
}
else
{
_sb.Append(_currentChar);
}
NextChar();
}
throw new InvalidDataException("syntax error, unterminated string literal");
}
case '{': CurrentToken = Token.OpenBrace; NextChar(); return;
case '}': CurrentToken = Token.CloseBrace; NextChar(); return;
case '[': CurrentToken = Token.OpenSquare; NextChar(); return;
case ']': CurrentToken = Token.CloseSquare; NextChar(); return;
case '=': CurrentToken = Token.Equal; NextChar(); return;
case ':': CurrentToken = Token.Colon; NextChar(); return;
case ';': CurrentToken = Token.SemiColon; NextChar(); return;
case ',': CurrentToken = Token.Comma; NextChar(); return;
case '\0': CurrentToken = Token.EOF; return;
}
// Number?
if (char.IsDigit(_currentChar) || _currentChar == '-')
{
TokenizeNumber();
return;
}
// Identifier? (checked for after everything else as identifiers are actually quite rare in valid json)
if (Char.IsLetter(_currentChar) || _currentChar == '_' || _currentChar == '$')
{
// Find end of identifier
_sb.Length = 0;
while (Char.IsLetterOrDigit(_currentChar) || _currentChar == '_' || _currentChar == '$')
{
_sb.Append(_currentChar);
NextChar();
}
String = _sb.ToString();
// Handle special identifiers
switch (String)
{
case "true":
LiteralKind = LiteralKind.True;
CurrentToken = Token.Literal;
return;
case "false":
LiteralKind = LiteralKind.False;
CurrentToken = Token.Literal;
return;
case "null":
LiteralKind = LiteralKind.Null;
CurrentToken = Token.Literal;
return;
}
CurrentToken = Token.Identifier;
return;
}
// What the?
throw new InvalidDataException(string.Format("syntax error, unexpected character '{0}'", _currentChar));
}
}
// Parse a sequence of characters that could make up a valid number
// For performance, we don't actually parse it into a number yet. When using PetaJsonEmit we parse
// later, directly into a value type to avoid boxing
private void TokenizeNumber()
{
_sb.Length = 0;
// Leading negative sign
bool signed = false;
if (_currentChar == '-')
{
signed = true;
_sb.Append(_currentChar);
NextChar();
}
// Hex prefix?
bool hex = false;
if (_currentChar == '0' && (_options & JsonOptions.StrictParser) == 0)
{
_sb.Append(_currentChar);
NextChar();
if (_currentChar == 'x' || _currentChar == 'X')
{
_sb.Append(_currentChar);
NextChar();
hex = true;
}
}
// Process characters, but vaguely figure out what type it is
bool cont = true;
bool fp = false;
while (cont)
{
switch (_currentChar)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
_sb.Append(_currentChar);
NextChar();
break;
case 'A':
case 'a':
case 'B':
case 'b':
case 'C':
case 'c':
case 'D':
case 'd':
case 'F':
case 'f':
if (!hex)
cont = false;
else
{
_sb.Append(_currentChar);
NextChar();
}
break;
case '.':
if (hex)
{
cont = false;
}
else
{
fp = true;
_sb.Append(_currentChar);
NextChar();
}
break;
case 'E':
case 'e':
if (!hex)
{
fp = true;
_sb.Append(_currentChar);
NextChar();
if (_currentChar == '+' || _currentChar == '-')
{
_sb.Append(_currentChar);
NextChar();
}
}
break;
default:
cont = false;
break;
}
}
if (char.IsLetter(_currentChar))
throw new InvalidDataException(string.Format("syntax error, invalid character following number '{0}'", _sb.ToString()));
// Setup token
String = _sb.ToString();
CurrentToken = Token.Literal;
// Setup literal kind
if (fp)
{
LiteralKind = LiteralKind.FloatingPoint;
}
else if (signed)
{
LiteralKind = LiteralKind.SignedInteger;
}
else
{
LiteralKind = LiteralKind.UnsignedInteger;
}
}
// Check the current token, throw exception if mismatch
public void Check(Token tokenRequired)
{
if (tokenRequired != CurrentToken)
{
throw new InvalidDataException(string.Format("syntax error, expected {0} found {1}", tokenRequired, CurrentToken));
}
}
// Skip token which must match
public void Skip(Token tokenRequired)
{
Check(tokenRequired);
NextToken();
}
// Skip token if it matches
public bool SkipIf(Token tokenRequired)
{
if (tokenRequired == CurrentToken)
{
NextToken();
return true;
}
return false;
}
}
#if !PETAJSON_NO_EMIT
static class Emit
{
// Generates a function that when passed an object of specified type, renders it to an IJsonReader
public static Action<IJsonWriter, object> MakeFormatter(Type type)
{
var formatJson = ReflectionInfo.FindFormatJson(type);
if (formatJson != null)
{
var method = new DynamicMethod("invoke_formatJson", null, new Type[] { typeof(IJsonWriter), typeof(Object) }, true);
var il = method.GetILGenerator();
if (formatJson.ReturnType == typeof(string))
{
// w.WriteStringLiteral(o.FormatJson())
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Unbox, type);
il.Emit(OpCodes.Call, formatJson);
il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteStringLiteral"));
}
else
{
// o.FormatJson(w);
il.Emit(OpCodes.Ldarg_1);
il.Emit(type.IsValueType ? OpCodes.Unbox : OpCodes.Castclass, type);
il.Emit(OpCodes.Ldarg_0);
il.Emit(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, formatJson);
}
il.Emit(OpCodes.Ret);
return (Action<IJsonWriter, object>)method.CreateDelegate(typeof(Action<IJsonWriter, object>));
}
else
{
// Get the reflection info for this type
var ri = ReflectionInfo.GetReflectionInfo(type);
if (ri == null)
return null;
// Create a dynamic method that can do the work
var method = new DynamicMethod("dynamic_formatter", null, new Type[] { typeof(IJsonWriter), typeof(object) }, true);
var il = method.GetILGenerator();
// Cast/unbox the target object and store in local variable
var locTypedObj = il.DeclareLocal(type);
il.Emit(OpCodes.Ldarg_1);
il.Emit(type.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, type);
il.Emit(OpCodes.Stloc, locTypedObj);
// Get Invariant CultureInfo (since we'll probably be needing this)
var locInvariant = il.DeclareLocal(typeof(IFormatProvider));
il.Emit(OpCodes.Call, typeof(CultureInfo).GetProperty("InvariantCulture").GetGetMethod());
il.Emit(OpCodes.Stloc, locInvariant);
// These are the types we'll call .ToString(Culture.InvariantCulture) on
var toStringTypes = new Type[] {
typeof(int), typeof(uint), typeof(long), typeof(ulong),
typeof(short), typeof(ushort), typeof(decimal),
typeof(byte), typeof(sbyte)
};
// Theses types we also generate for
var otherSupportedTypes = new Type[] {
typeof(double), typeof(float), typeof(string), typeof(char)
};
// Call IJsonWriting if implemented
if (typeof(IJsonWriting).IsAssignableFrom(type))
{
if (type.IsValueType)
{
il.Emit(OpCodes.Ldloca, locTypedObj);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, type.GetInterfaceMap(typeof(IJsonWriting)).TargetMethods[0]);
}
else
{
il.Emit(OpCodes.Ldloc, locTypedObj);
il.Emit(OpCodes.Castclass, typeof(IJsonWriting));
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, typeof(IJsonWriting).GetMethod("OnJsonWriting", new Type[] { typeof(IJsonWriter) }));
}
}
// Process all members
foreach (var m in ri.Members)
{
// Dont save deprecated properties
if (m.Deprecated)
{
continue;
}
// Ignore write only properties
var pi = m.Member as PropertyInfo;
if (pi != null && pi.GetGetMethod(true) == null)
{
continue;
}
// Write the Json key
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, m.JsonKey);
il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteKeyNoEscaping", new Type[] { typeof(string) }));
// Load the writer
il.Emit(OpCodes.Ldarg_0);
// Get the member type
var memberType = m.MemberType;
// Load the target object
if (type.IsValueType)
{
il.Emit(OpCodes.Ldloca, locTypedObj);
}
else
{
il.Emit(OpCodes.Ldloc, locTypedObj);
}
// Work out if we need the value or it's address on the stack
bool NeedValueAddress = (memberType.IsValueType && (toStringTypes.Contains(memberType) || otherSupportedTypes.Contains(memberType)));
if (Nullable.GetUnderlyingType(memberType) != null)
{
NeedValueAddress = true;
}
// Property?
if (pi != null)
{
// Call property's get method
if (type.IsValueType)
il.Emit(OpCodes.Call, pi.GetGetMethod(true));
else
il.Emit(OpCodes.Callvirt, pi.GetGetMethod(true));
// If we need the address then store in a local and take it's address
if (NeedValueAddress)
{
var locTemp = il.DeclareLocal(memberType);
il.Emit(OpCodes.Stloc, locTemp);
il.Emit(OpCodes.Ldloca, locTemp);
}
}
// Field?
var fi = m.Member as FieldInfo;
if (fi != null)
{
if (NeedValueAddress)
{
il.Emit(OpCodes.Ldflda, fi);
}
else
{
il.Emit(OpCodes.Ldfld, fi);
}
}
Label? lblFinished = null;
// Is it a nullable type?
var typeUnderlying = Nullable.GetUnderlyingType(memberType);
if (typeUnderlying != null)
{
// Duplicate the address so we can call get_HasValue() and then get_Value()
il.Emit(OpCodes.Dup);
// Define some labels
var lblHasValue = il.DefineLabel();
lblFinished = il.DefineLabel();
// Call has_Value
il.Emit(OpCodes.Call, memberType.GetProperty("HasValue").GetGetMethod());
il.Emit(OpCodes.Brtrue, lblHasValue);
// No value, write "null:
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldstr, "null");
il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) }));
il.Emit(OpCodes.Br_S, lblFinished.Value);
// Get it's value
il.MarkLabel(lblHasValue);
il.Emit(OpCodes.Call, memberType.GetProperty("Value").GetGetMethod());
// Switch to the underlying type from here on
memberType = typeUnderlying;
NeedValueAddress = (memberType.IsValueType && (toStringTypes.Contains(memberType) || otherSupportedTypes.Contains(memberType)));
// Work out again if we need the address of the value
if (NeedValueAddress)
{
var locTemp = il.DeclareLocal(memberType);
il.Emit(OpCodes.Stloc, locTemp);
il.Emit(OpCodes.Ldloca, locTemp);
}
}
// ToString()
if (toStringTypes.Contains(memberType))
{
// Convert to string
il.Emit(OpCodes.Ldloc, locInvariant);
il.Emit(OpCodes.Call, memberType.GetMethod("ToString", new Type[] { typeof(IFormatProvider) }));
il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) }));
}
// ToString("R")
else if (memberType == typeof(float) || memberType == typeof(double))
{
il.Emit(OpCodes.Ldstr, "R");
il.Emit(OpCodes.Ldloc, locInvariant);
il.Emit(OpCodes.Call, memberType.GetMethod("ToString", new Type[] { typeof(string), typeof(IFormatProvider) }));
il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) }));
}
// String?
else if (memberType == typeof(string))
{
il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteStringLiteral", new Type[] { typeof(string) }));
}
// Char?
else if (memberType == typeof(char))
{
il.Emit(OpCodes.Call, memberType.GetMethod("ToString", new Type[] { }));
il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteStringLiteral", new Type[] { typeof(string) }));
}
// Bool?
else if (memberType == typeof(bool))
{
var lblTrue = il.DefineLabel();
var lblCont = il.DefineLabel();
il.Emit(OpCodes.Brtrue_S, lblTrue);
il.Emit(OpCodes.Ldstr, "false");
il.Emit(OpCodes.Br_S, lblCont);
il.MarkLabel(lblTrue);
il.Emit(OpCodes.Ldstr, "true");
il.MarkLabel(lblCont);
il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) }));
}
// NB: We don't support DateTime as it's format can be changed
else
{
// Unsupported type, pass through
if (memberType.IsValueType)
{
il.Emit(OpCodes.Box, memberType);
}
il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteValue", new Type[] { typeof(object) }));
}
if (lblFinished.HasValue)
il.MarkLabel(lblFinished.Value);
}
// Call IJsonWritten
if (typeof(IJsonWritten).IsAssignableFrom(type))
{
if (type.IsValueType)
{
il.Emit(OpCodes.Ldloca, locTypedObj);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, type.GetInterfaceMap(typeof(IJsonWritten)).TargetMethods[0]);
}
else
{
il.Emit(OpCodes.Ldloc, locTypedObj);
il.Emit(OpCodes.Castclass, typeof(IJsonWriting));
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, typeof(IJsonWriting).GetMethod("OnJsonWritten", new Type[] { typeof(IJsonWriter) }));
}
}
// Done!
il.Emit(OpCodes.Ret);
var impl = (Action<IJsonWriter, object>)method.CreateDelegate(typeof(Action<IJsonWriter, object>));
// Wrap it in a call to WriteDictionary
return (w, obj) =>
{
w.WriteDictionary(() =>
{
impl(w, obj);
});
};
}
}
// Pseudo box lets us pass a value type by reference. Used during
// deserialization of value types.
interface IPseudoBox
{
object GetValue();
}
[Obfuscation(Exclude = true, ApplyToMembers = true)]
class PseudoBox<T> : IPseudoBox where T : struct
{
public T value = default(T);
object IPseudoBox.GetValue() { return value; }
}
// Make a parser for value types
public static Func<IJsonReader, Type, object> MakeParser(Type type)
{
System.Diagnostics.Debug.Assert(type.IsValueType);
// ParseJson method?
var parseJson = ReflectionInfo.FindParseJson(type);
if (parseJson != null)
{
if (parseJson.GetParameters()[0].ParameterType == typeof(IJsonReader))
{
var method = new DynamicMethod("invoke_ParseJson", typeof(Object), new Type[] { typeof(IJsonReader), typeof(Type) }, true);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, parseJson);
il.Emit(OpCodes.Box, type);
il.Emit(OpCodes.Ret);
return (Func<IJsonReader, Type, object>)method.CreateDelegate(typeof(Func<IJsonReader, Type, object>));
}
else
{
var method = new DynamicMethod("invoke_ParseJson", typeof(Object), new Type[] { typeof(string) }, true);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, parseJson);
il.Emit(OpCodes.Box, type);
il.Emit(OpCodes.Ret);
var invoke = (Func<string, object>)method.CreateDelegate(typeof(Func<string, object>));
return (r, t) =>
{
if (r.GetLiteralKind() == LiteralKind.String)
{
var o = invoke(r.GetLiteralString());
r.NextToken();
return o;
}
throw new InvalidDataException(string.Format("Expected string literal for type {0}", type.FullName));
};
}
}
else
{
// Get the reflection info for this type
var ri = ReflectionInfo.GetReflectionInfo(type);
if (ri == null)
return null;
// We'll create setters for each property/field
var setters = new Dictionary<string, Action<IJsonReader, object>>();
// Store the value in a pseudo box until it's fully initialized
var boxType = typeof(PseudoBox<>).MakeGenericType(type);
// Process all members
foreach (var m in ri.Members)
{
// Ignore write only properties
var pi = m.Member as PropertyInfo;
var fi = m.Member as FieldInfo;
if (pi != null && pi.GetSetMethod(true) == null)
{
continue;
}
// Create a dynamic method that can do the work
var method = new DynamicMethod("dynamic_parser", null, new Type[] { typeof(IJsonReader), typeof(object) }, true);
var il = method.GetILGenerator();
// Load the target
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Castclass, boxType);
il.Emit(OpCodes.Ldflda, boxType.GetField("value"));
// Get the value
GenerateGetJsonValue(m, il);
// Assign it
if (pi != null)
il.Emit(OpCodes.Call, pi.GetSetMethod(true));
if (fi != null)
il.Emit(OpCodes.Stfld, fi);
// Done
il.Emit(OpCodes.Ret);
// Store in the map of setters
setters.Add(m.JsonKey, (Action<IJsonReader, object>)method.CreateDelegate(typeof(Action<IJsonReader, object>)));
}
// Create helpers to invoke the interfaces (this is painful but avoids having to really box
// the value in order to call the interface).
Action<object, IJsonReader> invokeLoading = MakeInterfaceCall(type, typeof(IJsonLoading));
Action<object, IJsonReader> invokeLoaded = MakeInterfaceCall(type, typeof(IJsonLoaded));
Func<object, IJsonReader, string, bool> invokeField = MakeLoadFieldCall(type);
// Create the parser
Func<IJsonReader, Type, object> parser = (reader, Type) =>
{
// Create pseudobox (ie: new PseudoBox<Type>)
var box = DecoratingActivator.CreateInstance(boxType);
// Call IJsonLoading
if (invokeLoading != null)
invokeLoading(box, reader);
// Read the dictionary
reader.ParseDictionary(key =>
{
// Call IJsonLoadField
if (invokeField != null && invokeField(box, reader, key))
return;
// Get a setter and invoke it if found
Action<IJsonReader, object> setter;
if (setters.TryGetValue(key, out setter))
{
setter(reader, box);
}
});
// IJsonLoaded
if (invokeLoaded != null)
invokeLoaded(box, reader);
// Return the value
return ((IPseudoBox)box).GetValue();
};
// Done
return parser;
}
}
// Helper to make the call to a PsuedoBox value's IJsonLoading or IJsonLoaded
static Action<object, IJsonReader> MakeInterfaceCall(Type type, Type tItf)
{
// Interface supported?
if (!tItf.IsAssignableFrom(type))
return null;
// Resolve the box type
var boxType = typeof(PseudoBox<>).MakeGenericType(type);
// Create method
var method = new DynamicMethod("dynamic_invoke_" + tItf.Name, null, new Type[] { typeof(object), typeof(IJsonReader) }, true);
var il = method.GetILGenerator();
// Call interface method
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, boxType);
il.Emit(OpCodes.Ldflda, boxType.GetField("value"));
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, type.GetInterfaceMap(tItf).TargetMethods[0]);
il.Emit(OpCodes.Ret);
// Done
return (Action<object, IJsonReader>)method.CreateDelegate(typeof(Action<object, IJsonReader>));
}
// Similar to above but for IJsonLoadField
static Func<object, IJsonReader, string, bool> MakeLoadFieldCall(Type type)
{
// Interface supported?
var tItf = typeof(IJsonLoadField);
if (!tItf.IsAssignableFrom(type))
return null;
// Resolve the box type
var boxType = typeof(PseudoBox<>).MakeGenericType(type);
// Create method
var method = new DynamicMethod("dynamic_invoke_" + tItf.Name, typeof(bool), new Type[] { typeof(object), typeof(IJsonReader), typeof(string) }, true);
var il = method.GetILGenerator();
// Call interface method
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, boxType);
il.Emit(OpCodes.Ldflda, boxType.GetField("value"));
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Call, type.GetInterfaceMap(tItf).TargetMethods[0]);
il.Emit(OpCodes.Ret);
// Done
return (Func<object, IJsonReader, string, bool>)method.CreateDelegate(typeof(Func<object, IJsonReader, string, bool>));
}
// Create an "into parser" that can parse from IJsonReader into a reference type (ie: a class)
public static Action<IJsonReader, object> MakeIntoParser(Type type)
{
System.Diagnostics.Debug.Assert(!type.IsValueType);
// Get the reflection info for this type
var ri = ReflectionInfo.GetReflectionInfo(type);
if (ri == null)
return null;
// We'll create setters for each property/field
var setters = new Dictionary<string, Action<IJsonReader, object>>();
// Process all members
foreach (var m in ri.Members)
{
// Ignore write only properties
var pi = m.Member as PropertyInfo;
var fi = m.Member as FieldInfo;
if (pi != null && pi.GetSetMethod(true) == null)
{
continue;
}
// Ignore read only properties that has KeepInstance attribute
if (pi != null && pi.GetGetMethod(true) == null && m.KeepInstance)
{
continue;
}
// Create a dynamic method that can do the work
var method = new DynamicMethod("dynamic_parser", null, new Type[] { typeof(IJsonReader), typeof(object) }, true);
var il = method.GetILGenerator();
// Load the target
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Castclass, type);
// Try to keep existing instance?
if (m.KeepInstance)
{
// Get existing existing instance
il.Emit(OpCodes.Dup);
if (pi != null)
il.Emit(OpCodes.Callvirt, pi.GetGetMethod(true));
else
il.Emit(OpCodes.Ldfld, fi);
var existingInstance = il.DeclareLocal(m.MemberType);
var lblExistingInstanceNull = il.DefineLabel();
// Keep a copy of the existing instance in a locale
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Stloc, existingInstance);
// Compare to null
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Brtrue_S, lblExistingInstanceNull);
il.Emit(OpCodes.Ldarg_0); // reader
il.Emit(OpCodes.Ldloc, existingInstance); // into
il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("ParseInto", new Type[] { typeof(Object) }));
il.Emit(OpCodes.Pop); // Clean up target left on stack (1)
il.Emit(OpCodes.Ret);
il.MarkLabel(lblExistingInstanceNull);
}
// Get the value from IJsonReader
GenerateGetJsonValue(m, il);
// Assign it
if (pi != null)
il.Emit(OpCodes.Callvirt, pi.GetSetMethod(true));
if (fi != null)
il.Emit(OpCodes.Stfld, fi);
// Done
il.Emit(OpCodes.Ret);
// Store the handler in map
setters.Add(m.JsonKey, (Action<IJsonReader, object>)method.CreateDelegate(typeof(Action<IJsonReader, object>)));
}
// Now create the parseInto delegate
Action<IJsonReader, object> parseInto = (reader, obj) =>
{
// Call IJsonLoading
var loading = obj as IJsonLoading;
if (loading != null)
loading.OnJsonLoading(reader);
// Cache IJsonLoadField
var lf = obj as IJsonLoadField;
// Read dictionary keys
reader.ParseDictionary(key =>
{
// Call IJsonLoadField
if (lf != null && lf.OnJsonField(reader, key))
return;
// Call setters
Action<IJsonReader, object> setter;
if (setters.TryGetValue(key, out setter))
{
setter(reader, obj);
}
});
// Call IJsonLoaded
var loaded = obj as IJsonLoaded;
if (loaded != null)
loaded.OnJsonLoaded(reader);
};
// Since we've created the ParseInto handler, we might as well register
// as a Parse handler too.
RegisterIntoParser(type, parseInto);
// Done
return parseInto;
}
// Registers a ParseInto handler as Parse handler that instantiates the object
// and then parses into it.
static void RegisterIntoParser(Type type, Action<IJsonReader, object> parseInto)
{
// Check type has a parameterless constructor
var con = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null);
if (con == null)
return;
// Create a dynamic method that can do the work
var method = new DynamicMethod("dynamic_factory", typeof(object), new Type[] { typeof(IJsonReader), typeof(Action<IJsonReader, object>) }, true);
var il = method.GetILGenerator();
// Create the new object
var locObj = il.DeclareLocal(typeof(object));
il.Emit(OpCodes.Newobj, con);
il.Emit(OpCodes.Dup); // For return value
il.Emit(OpCodes.Stloc, locObj);
il.Emit(OpCodes.Ldarg_1); // parseinto delegate
il.Emit(OpCodes.Ldarg_0); // IJsonReader
il.Emit(OpCodes.Ldloc, locObj); // new object instance
il.Emit(OpCodes.Callvirt, typeof(Action<IJsonReader, object>).GetMethod("Invoke"));
il.Emit(OpCodes.Ret);
var factory = (Func<IJsonReader, Action<IJsonReader, object>, object>)method.CreateDelegate(typeof(Func<IJsonReader, Action<IJsonReader, object>, object>));
Json.RegisterParser(type, (reader, type2) =>
{
return factory(reader, parseInto);
});
}
// Generate the MSIL to retrieve a value for a particular field or property from a IJsonReader
private static void GenerateGetJsonValue(JsonMemberInfo m, ILGenerator il)
{
Action<string> generateCallToHelper = helperName =>
{
// Call the helper
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, typeof(Emit).GetMethod(helperName, new Type[] { typeof(IJsonReader) }));
// Move to next token
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("NextToken", new Type[] { }));
};
Type[] numericTypes = new Type[] {
typeof(int), typeof(uint), typeof(long), typeof(ulong),
typeof(short), typeof(ushort), typeof(decimal),
typeof(byte), typeof(sbyte),
typeof(double), typeof(float)
};
if (m.MemberType == typeof(string))
{
generateCallToHelper("GetLiteralString");
}
else if (m.MemberType == typeof(bool))
{
generateCallToHelper("GetLiteralBool");
}
else if (m.MemberType == typeof(char))
{
generateCallToHelper("GetLiteralChar");
}
else if (numericTypes.Contains(m.MemberType))
{
// Get raw number string
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, typeof(Emit).GetMethod("GetLiteralNumber", new Type[] { typeof(IJsonReader) }));
// Convert to a string
il.Emit(OpCodes.Call, typeof(CultureInfo).GetProperty("InvariantCulture").GetGetMethod());
il.Emit(OpCodes.Call, m.MemberType.GetMethod("Parse", new Type[] { typeof(string), typeof(IFormatProvider) }));
//
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("NextToken", new Type[] { }));
}
else
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldtoken, m.MemberType);
il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) }));
il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("Parse", new Type[] { typeof(Type) }));
il.Emit(m.MemberType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, m.MemberType);
}
}
// Helper to fetch a literal bool from an IJsonReader
[Obfuscation(Exclude = true)]
public static bool GetLiteralBool(IJsonReader r)
{
switch (r.GetLiteralKind())
{
case LiteralKind.True:
return true;
case LiteralKind.False:
return false;
default:
throw new InvalidDataException("expected a boolean value");
}
}
// Helper to fetch a literal character from an IJsonReader
[Obfuscation(Exclude = true)]
public static char GetLiteralChar(IJsonReader r)
{
if (r.GetLiteralKind() != LiteralKind.String)
throw new InvalidDataException("expected a single character string literal");
var str = r.GetLiteralString();
if (str == null || str.Length != 1)
throw new InvalidDataException("expected a single character string literal");
return str[0];
}
// Helper to fetch a literal string from an IJsonReader
[Obfuscation(Exclude = true)]
public static string GetLiteralString(IJsonReader r)
{
switch (r.GetLiteralKind())
{
case LiteralKind.Null: return null;
case LiteralKind.String: return r.GetLiteralString();
}
throw new InvalidDataException("expected a string literal");
}
// Helper to fetch a literal number from an IJsonReader (returns the raw string)
[Obfuscation(Exclude = true)]
public static string GetLiteralNumber(IJsonReader r)
{
switch (r.GetLiteralKind())
{
case LiteralKind.SignedInteger:
case LiteralKind.UnsignedInteger:
case LiteralKind.FloatingPoint:
return r.GetLiteralString();
}
throw new InvalidDataException("expected a numeric literal");
}
}
#endif
}
}
Donate
This software may be used free of charge, but as with all free software there are costs involved to develop and maintain.
If this site or its services have saved you time, please consider a donation to help with running costs and timely updates.
Donate