a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
545 lines
16 KiB
C#
545 lines
16 KiB
C#
//
|
|
// JsonSerializer.cs
|
|
//
|
|
// Author:
|
|
// Marek Habersack <mhabersack@novell.com>
|
|
//
|
|
// (C) 2008 Novell, Inc. http://novell.com/
|
|
//
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
|
|
namespace System.Web.Script.Serialization
|
|
{
|
|
internal sealed class JsonSerializer
|
|
{
|
|
internal static readonly long InitialJavaScriptDateTicks = new DateTime (1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
|
|
static readonly DateTime MinimumJavaScriptDate = new DateTime (100, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
|
static readonly MethodInfo serializeGenericDictionary = typeof (JsonSerializer).GetMethod ("SerializeGenericDictionary", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
Dictionary <object, bool> objectCache;
|
|
JavaScriptSerializer serializer;
|
|
JavaScriptTypeResolver typeResolver;
|
|
int recursionLimit;
|
|
int maxJsonLength;
|
|
int recursionDepth;
|
|
|
|
Dictionary <Type, MethodInfo> serializeGenericDictionaryMethods;
|
|
|
|
public JsonSerializer (JavaScriptSerializer serializer)
|
|
{
|
|
if (serializer == null)
|
|
throw new ArgumentNullException ("serializer");
|
|
this.serializer = serializer;
|
|
typeResolver = serializer.TypeResolver;
|
|
recursionLimit = serializer.RecursionLimit;
|
|
maxJsonLength = serializer.MaxJsonLength;
|
|
}
|
|
|
|
public void Serialize (object obj, StringBuilder output)
|
|
{
|
|
if (output == null)
|
|
throw new ArgumentNullException ("output");
|
|
|
|
DoSerialize (obj, output);
|
|
}
|
|
|
|
public void Serialize (object obj, TextWriter output)
|
|
{
|
|
if (output == null)
|
|
throw new ArgumentNullException ("output");
|
|
|
|
StringBuilder sb = new StringBuilder ();
|
|
DoSerialize (obj, sb);
|
|
output.Write (sb.ToString ());
|
|
}
|
|
|
|
void DoSerialize (object obj, StringBuilder output)
|
|
{
|
|
recursionDepth = 0;
|
|
objectCache = new Dictionary <object, bool> ();
|
|
SerializeValue (obj, output);
|
|
}
|
|
|
|
void SerializeValue (object obj, StringBuilder output)
|
|
{
|
|
recursionDepth++;
|
|
SerializeValueImpl (obj, output);
|
|
recursionDepth--;
|
|
}
|
|
|
|
void SerializeValueImpl (object obj, StringBuilder output)
|
|
{
|
|
if (recursionDepth > recursionLimit)
|
|
throw new ArgumentException ("Recursion limit has been exceeded while serializing object of type '{0}'", obj != null ? obj.GetType ().ToString () : "[null]");
|
|
|
|
if (obj == null || DBNull.Value.Equals (obj)) {
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "null");
|
|
return;
|
|
}
|
|
|
|
Type valueType = obj.GetType ();
|
|
JavaScriptConverter jsc = serializer.GetConverter (valueType);
|
|
if (jsc != null) {
|
|
IDictionary <string, object> result = jsc.Serialize (obj, serializer);
|
|
|
|
if (result == null) {
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "null");
|
|
return;
|
|
}
|
|
|
|
if (typeResolver != null) {
|
|
string typeId = typeResolver.ResolveTypeId (valueType);
|
|
if (!String.IsNullOrEmpty (typeId))
|
|
result [JavaScriptSerializer.SerializedTypeNameKey] = typeId;
|
|
}
|
|
|
|
SerializeValue (result, output);
|
|
return;
|
|
}
|
|
|
|
TypeCode typeCode = Type.GetTypeCode (valueType);
|
|
switch (typeCode) {
|
|
case TypeCode.String:
|
|
WriteValue (output, (string)obj);
|
|
return;
|
|
|
|
case TypeCode.Char:
|
|
WriteValue (output, (char)obj);
|
|
return;
|
|
|
|
case TypeCode.Boolean:
|
|
WriteValue (output, (bool)obj);
|
|
return;
|
|
|
|
case TypeCode.SByte:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.Byte:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.UInt64:
|
|
if (valueType.IsEnum) {
|
|
WriteEnumValue (output, obj, typeCode);
|
|
return;
|
|
}
|
|
goto case TypeCode.Decimal;
|
|
|
|
case TypeCode.Single:
|
|
WriteValue (output, (float)obj);
|
|
return;
|
|
|
|
case TypeCode.Double:
|
|
WriteValue (output, (double)obj);
|
|
return;
|
|
|
|
case TypeCode.Decimal:
|
|
WriteValue (output, obj as IConvertible);
|
|
return;
|
|
|
|
case TypeCode.DateTime:
|
|
WriteValue (output, (DateTime)obj);
|
|
return;
|
|
}
|
|
|
|
if (typeof (Uri).IsAssignableFrom (valueType)) {
|
|
WriteValue (output, (Uri)obj);
|
|
return;
|
|
}
|
|
|
|
if (typeof (Guid).IsAssignableFrom (valueType)) {
|
|
WriteValue (output, (Guid)obj);
|
|
return;
|
|
}
|
|
|
|
IConvertible convertible = obj as IConvertible;
|
|
if (convertible != null) {
|
|
WriteValue (output, convertible);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (objectCache.ContainsKey (obj))
|
|
throw new InvalidOperationException ("Circular reference detected.");
|
|
objectCache.Add (obj, true);
|
|
|
|
Type closedIDict = GetClosedIDictionaryBase(valueType);
|
|
if (closedIDict != null) {
|
|
if (serializeGenericDictionaryMethods == null)
|
|
serializeGenericDictionaryMethods = new Dictionary <Type, MethodInfo> ();
|
|
|
|
MethodInfo mi;
|
|
if (!serializeGenericDictionaryMethods.TryGetValue (closedIDict, out mi)) {
|
|
Type[] types = closedIDict.GetGenericArguments ();
|
|
mi = serializeGenericDictionary.MakeGenericMethod (types [0], types [1]);
|
|
serializeGenericDictionaryMethods.Add (closedIDict, mi);
|
|
}
|
|
|
|
mi.Invoke (this, new object[] {output, obj});
|
|
return;
|
|
}
|
|
|
|
IDictionary dict = obj as IDictionary;
|
|
if (dict != null) {
|
|
SerializeDictionary (output, dict);
|
|
return;
|
|
}
|
|
|
|
IEnumerable enumerable = obj as IEnumerable;
|
|
if (enumerable != null) {
|
|
SerializeEnumerable (output, enumerable);
|
|
return;
|
|
}
|
|
|
|
SerializeArbitraryObject (output, obj, valueType);
|
|
} finally {
|
|
objectCache.Remove (obj);
|
|
}
|
|
}
|
|
|
|
Type GetClosedIDictionaryBase(Type t) {
|
|
if(t.IsGenericType && typeof (IDictionary <,>).IsAssignableFrom (t.GetGenericTypeDefinition ()))
|
|
return t;
|
|
|
|
foreach(Type iface in t.GetInterfaces()) {
|
|
if(iface.IsGenericType && typeof (IDictionary <,>).IsAssignableFrom (iface.GetGenericTypeDefinition ()))
|
|
return iface;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
bool ShouldIgnoreMember (MemberInfo mi, out MethodInfo getMethod)
|
|
{
|
|
getMethod = null;
|
|
if (mi == null)
|
|
return true;
|
|
|
|
if (mi.IsDefined (typeof (ScriptIgnoreAttribute), true))
|
|
return true;
|
|
|
|
FieldInfo fi = mi as FieldInfo;
|
|
if (fi != null)
|
|
return false;
|
|
|
|
PropertyInfo pi = mi as PropertyInfo;
|
|
if (pi == null)
|
|
return true;
|
|
|
|
getMethod = pi.GetGetMethod ();
|
|
if (getMethod == null || getMethod.GetParameters ().Length > 0) {
|
|
getMethod = null;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
object GetMemberValue (object obj, MemberInfo mi)
|
|
{
|
|
FieldInfo fi = mi as FieldInfo;
|
|
|
|
if (fi != null)
|
|
return fi.GetValue (obj);
|
|
|
|
MethodInfo method = mi as MethodInfo;
|
|
if (method == null)
|
|
throw new InvalidOperationException ("Member is not a method (internal error).");
|
|
|
|
object ret;
|
|
|
|
try {
|
|
ret = method.Invoke (obj, null);
|
|
} catch (TargetInvocationException niex) {
|
|
if (niex.InnerException is NotImplementedException) {
|
|
Console.WriteLine ("!!! COMPATIBILITY WARNING. FEATURE NOT IMPLEMENTED. !!!");
|
|
Console.WriteLine (niex);
|
|
Console.WriteLine ("!!! RETURNING NULL. PLEASE LET MONO DEVELOPERS KNOW ABOUT THIS EXCEPTION. !!!");
|
|
return null;
|
|
}
|
|
|
|
throw;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void SerializeArbitraryObject (StringBuilder output, object obj, Type type)
|
|
{
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "{");
|
|
|
|
bool first = true;
|
|
if (typeResolver != null) {
|
|
string typeId = typeResolver.ResolveTypeId (type);
|
|
if (!String.IsNullOrEmpty (typeId)) {
|
|
WriteDictionaryEntry (output, first, JavaScriptSerializer.SerializedTypeNameKey, typeId);
|
|
first = false;
|
|
}
|
|
}
|
|
|
|
SerializeMembers <FieldInfo> (type.GetFields (BindingFlags.Public | BindingFlags.Instance), obj, output, ref first);
|
|
SerializeMembers <PropertyInfo> (type.GetProperties (BindingFlags.Public | BindingFlags.Instance), obj, output, ref first);
|
|
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "}");
|
|
}
|
|
|
|
void SerializeMembers <T> (T[] members, object obj, StringBuilder output, ref bool first) where T: MemberInfo
|
|
{
|
|
MemberInfo member;
|
|
MethodInfo getMethod;
|
|
string name;
|
|
|
|
foreach (T mi in members) {
|
|
if (ShouldIgnoreMember (mi as MemberInfo, out getMethod))
|
|
continue;
|
|
|
|
name = mi.Name;
|
|
if (getMethod != null)
|
|
member = getMethod;
|
|
else
|
|
member = mi;
|
|
|
|
WriteDictionaryEntry (output, first, name, GetMemberValue (obj, member));
|
|
if (first)
|
|
first = false;
|
|
}
|
|
}
|
|
|
|
void SerializeEnumerable (StringBuilder output, IEnumerable enumerable)
|
|
{
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "[");
|
|
bool first = true;
|
|
foreach (object value in enumerable) {
|
|
if (!first)
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, ',');
|
|
SerializeValue (value, output);
|
|
if (first)
|
|
first = false;
|
|
}
|
|
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "]");
|
|
}
|
|
|
|
void SerializeDictionary (StringBuilder output, IDictionary dict)
|
|
{
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "{");
|
|
bool first = true;
|
|
|
|
foreach (DictionaryEntry entry in dict) {
|
|
WriteDictionaryEntry (output, first, entry.Key as string, entry.Value);
|
|
if (first)
|
|
first = false;
|
|
}
|
|
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "}");
|
|
}
|
|
|
|
void SerializeGenericDictionary <TKey, TValue> (StringBuilder output, IDictionary <TKey, TValue> dict)
|
|
{
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "{");
|
|
bool first = true;
|
|
|
|
foreach (KeyValuePair <TKey, TValue> kvp in dict) {
|
|
WriteDictionaryEntry (output, first, kvp.Key as string, kvp.Value);
|
|
if (first)
|
|
first = false;
|
|
}
|
|
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "}");
|
|
}
|
|
|
|
void WriteDictionaryEntry (StringBuilder output, bool skipComma, string key, object value)
|
|
{
|
|
if (key == null)
|
|
throw new InvalidOperationException ("Only dictionaries with keys convertible to string are supported.");
|
|
|
|
if (!skipComma)
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, ',');
|
|
|
|
WriteValue (output, key);
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, ':');
|
|
SerializeValue (value, output);
|
|
}
|
|
|
|
void WriteEnumValue (StringBuilder output, object value, TypeCode typeCode)
|
|
{
|
|
switch (typeCode) {
|
|
case TypeCode.SByte:
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, (sbyte)value);
|
|
return;
|
|
|
|
case TypeCode.Int16:
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, (short)value);
|
|
return;
|
|
|
|
case TypeCode.UInt16:
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, (ushort)value);
|
|
return;
|
|
|
|
case TypeCode.Int32:
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, (int)value);
|
|
return;
|
|
|
|
case TypeCode.Byte:
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, (byte)value);
|
|
return;
|
|
|
|
case TypeCode.UInt32:
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, (uint)value);
|
|
return;
|
|
|
|
case TypeCode.Int64:
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, (long)value);
|
|
return;
|
|
|
|
case TypeCode.UInt64:
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, (ulong)value);
|
|
return;
|
|
|
|
default:
|
|
throw new InvalidOperationException (String.Format ("Invalid type code for enum: {0}", typeCode));
|
|
}
|
|
}
|
|
|
|
void WriteValue (StringBuilder output, float value)
|
|
{
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, value.ToString ("r", CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
void WriteValue (StringBuilder output, double value)
|
|
{
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, value.ToString ("r", CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
void WriteValue (StringBuilder output, Guid value)
|
|
{
|
|
WriteValue (output, value.ToString ());
|
|
}
|
|
|
|
void WriteValue (StringBuilder output, Uri value)
|
|
{
|
|
WriteValue (output, value.OriginalString);
|
|
}
|
|
|
|
void WriteValue (StringBuilder output, DateTime value)
|
|
{
|
|
value = value.ToUniversalTime ();
|
|
|
|
if (value < MinimumJavaScriptDate)
|
|
value = MinimumJavaScriptDate;
|
|
|
|
long ticks = (value.Ticks - InitialJavaScriptDateTicks) / (long)10000;
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "\"\\/Date(" + ticks + ")\\/\"");
|
|
}
|
|
|
|
void WriteValue (StringBuilder output, IConvertible value)
|
|
{
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, value.ToString (CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
void WriteValue (StringBuilder output, bool value)
|
|
{
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, value ? "true" : "false");
|
|
}
|
|
|
|
void WriteValue (StringBuilder output, char value)
|
|
{
|
|
if (value == '\0') {
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "null");
|
|
return;
|
|
}
|
|
|
|
WriteValue (output, value.ToString ());
|
|
}
|
|
|
|
void WriteValue (StringBuilder output, string value)
|
|
{
|
|
if (String.IsNullOrEmpty (value)) {
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "\"\"");
|
|
return;
|
|
}
|
|
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "\"");
|
|
|
|
char c;
|
|
for (int i = 0; i < value.Length; i++) {
|
|
c = value [i];
|
|
|
|
switch (c) {
|
|
case '\t':
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, @"\t");
|
|
break;
|
|
case '\n':
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, @"\n");
|
|
break;
|
|
case '\r':
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, @"\r");
|
|
break;
|
|
case '\f':
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, @"\f");
|
|
break;
|
|
case '\b':
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, @"\b");
|
|
break;
|
|
case '<':
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, @"\u003c");
|
|
break;
|
|
case '>':
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, @"\u003e");
|
|
break;
|
|
case '"':
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "\\\"");
|
|
break;
|
|
case '\'':
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, @"\u0027");
|
|
break;
|
|
case '\\':
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, @"\\");
|
|
break;
|
|
default:
|
|
if (c > '\u001f')
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, c);
|
|
else {
|
|
output.Append("\\u00");
|
|
int intVal = (int) c;
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, (char) ('0' + (intVal >> 4)));
|
|
intVal &= 0xf;
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, (char) (intVal < 10 ? '0' + intVal : 'a' + (intVal - 10)));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
StringBuilderExtensions.AppendCount (output, maxJsonLength, "\"");
|
|
}
|
|
}
|
|
}
|