// // JsonSerializer.cs // // Author: // Marek Habersack // // (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 objectCache; JavaScriptSerializer serializer; JavaScriptTypeResolver typeResolver; int recursionLimit; int maxJsonLength; int recursionDepth; Dictionary 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 (); 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 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 (); 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 (type.GetFields (BindingFlags.Public | BindingFlags.Instance), obj, output, ref first); SerializeMembers (type.GetProperties (BindingFlags.Public | BindingFlags.Instance), obj, output, ref first); StringBuilderExtensions.AppendCount (output, maxJsonLength, "}"); } void SerializeMembers (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 (StringBuilder output, IDictionary dict) { StringBuilderExtensions.AppendCount (output, maxJsonLength, "{"); bool first = true; foreach (KeyValuePair 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, "\""); } } }