2 // JsonSerializationReader.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2008 Novell, Inc (http://www.novell.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Collections.ObjectModel;
32 using System.Globalization;
35 using System.Reflection;
39 namespace System.Runtime.Serialization.Json
41 class JsonSerializationReader
43 DataContractJsonSerializer serializer;
45 int serialized_object_count;
46 bool verify_object_name;
47 Dictionary<Type, TypeMap> typemaps = new Dictionary<Type, TypeMap> ();
50 public JsonSerializationReader (DataContractJsonSerializer serializer, XmlReader reader, Type rootType, bool verifyObjectName)
52 this.serializer = serializer;
54 this.root_type = rootType;
55 this.verify_object_name = verifyObjectName;
58 public XmlReader Reader {
59 get { return reader; }
62 public object ReadRoot ()
64 TypeMap rootMap = GetTypeMap (root_type);
66 object v = ReadObject (root_type);
70 public object ReadObject (Type type)
72 return ReadObject (type, null);
75 public object ReadObject (Type type, object instance)
77 if (serialized_object_count ++ == serializer.MaxItemsInObjectGraph)
78 throw SerializationError (String.Format ("The object graph exceeded the maximum object count '{0}' specified in the serializer", serializer.MaxItemsInObjectGraph));
80 bool nullable = false;
81 if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>)) {
83 type = Nullable.GetUnderlyingType (type);
86 bool isNull = reader.GetAttribute ("type") == "null";
88 switch (Type.GetTypeCode (type)) {
90 string dbn = reader.ReadElementContentAsString ();
91 if (dbn != String.Empty)
92 throw new SerializationException (String.Format ("The only expected DBNull value string is '{{}}'. Tha actual input was '{0}'.", dbn));
96 reader.ReadElementContentAsString ();
100 return reader.ReadElementContentAsString ();
102 var c = reader.ReadElementContentAsString ();
104 throw new XmlException ("Invalid JSON char");
105 return Char.Parse(c);
106 case TypeCode.Single:
107 case TypeCode.Double:
108 case TypeCode.Decimal:
109 return ReadValueType (type, nullable);
114 case TypeCode.UInt16:
115 case TypeCode.UInt32:
118 return Enum.ToObject (type, Convert.ChangeType (reader.ReadElementContentAsLong (), Enum.GetUnderlyingType (type), null));
120 return ReadValueType (type, nullable);
121 case TypeCode.UInt64:
123 return Enum.ToObject (type, Convert.ChangeType (reader.ReadElementContentAsDecimal (), Enum.GetUnderlyingType (type), null));
125 return ReadValueType (type, nullable);
126 case TypeCode.Boolean:
127 return ReadValueType (type, nullable);
128 case TypeCode.DateTime:
129 // it does not use ReadElementContentAsDateTime(). Different string format.
130 var s = reader.ReadElementContentAsString ();
131 if (s.Length < 2 || !s.StartsWith ("/Date(", StringComparison.Ordinal) || !s.EndsWith (")/", StringComparison.Ordinal)) {
134 throw new XmlException ("Invalid JSON DateTime format. The value format should be '/Date(UnixTime)/'");
137 // The date can contain [SIGN]LONG, [SIGN]LONG+HOURSMINUTES or [SIGN]LONG-HOURSMINUTES
138 // the format for HOURSMINUTES is DDDD
139 int tidx = s.IndexOf ('-', 8);
141 tidx = s.IndexOf ('+', 8);
144 s = s.Substring (6, s.Length - 8);
147 int.TryParse (s.Substring (tidx+1, s.Length-3-tidx), out offset);
149 minutes = (offset % 100) + (offset / 100) * 60;
153 s = s.Substring (6, tidx-6);
155 var date = new DateTime (1970, 1, 1).AddMilliseconds (long.Parse (s));
157 date = date.AddMinutes (minutes);
160 if (type == typeof (Guid)) {
161 return new Guid (reader.ReadElementContentAsString ());
162 } else if (type == typeof (Uri)) {
164 reader.ReadElementContentAsString ();
168 return new Uri (reader.ReadElementContentAsString (), UriKind.RelativeOrAbsolute);
169 } else if (type == typeof (XmlQualifiedName)) {
170 s = reader.ReadElementContentAsString ();
171 int idx = s.IndexOf (':');
172 return idx < 0 ? new XmlQualifiedName (s) : new XmlQualifiedName (s.Substring (0, idx), s.Substring (idx + 1));
173 } else if (type != typeof (object)) {
174 // strongly-typed object
175 if (reader.IsEmptyElement) {
176 // empty -> null array or object
181 Type ct = GetCollectionElementType (type);
183 return DeserializeGenericCollection (type, ct, instance);
185 string typeHint = reader.GetAttribute ("__type");
186 if (typeHint != null) {
187 // this might be a derived & known type. We allow it when it's both.
188 Type exactType = GetRuntimeType (typeHint, type);
189 if (exactType == null)
190 throw SerializationError (String.Format ("Cannot load type '{0}'", typeHint));
191 TypeMap map = GetTypeMap (exactType);
192 return map.Deserialize (this, instance);
193 } else { // no type hint
194 TypeMap map = GetTypeMap (type);
195 return map.Deserialize (this, instance);
200 return ReadInstanceDrivenObject ();
204 object ReadValueType (Type type, bool nullable)
206 string s = reader.ReadElementContentAsString ();
207 return nullable && s.Trim ().Length == 0 ? null : Convert.ChangeType (s, type, CultureInfo.InvariantCulture);
211 Type GetRuntimeType (string name, Type baseType)
213 string properName = ToRuntimeTypeName (name);
215 if (baseType != null && baseType.FullName.Equals (properName))
218 if (serializer.KnownTypes != null)
219 foreach (Type t in serializer.KnownTypes)
220 if (t.FullName.Equals (properName))
223 if (baseType != null)
224 foreach (var attr in baseType.GetCustomAttributes (typeof (KnownTypeAttribute), false))
225 if ((attr as KnownTypeAttribute).Type.FullName.Equals (properName))
226 return (attr as KnownTypeAttribute).Type;
231 object ReadInstanceDrivenObject ()
233 string type = reader.GetAttribute ("type");
239 string runtimeType = reader.GetAttribute ("__type");
240 if (runtimeType != null) {
241 Type t = GetRuntimeType (runtimeType, null);
243 throw SerializationError (String.Format ("Cannot load type '{0}'", runtimeType));
244 return ReadObject (t);
248 string v = reader.ReadElementContentAsString ();
257 throw SerializationError (String.Format ("Invalid JSON boolean value: {0}", v));
263 if (int.TryParse (v, NumberStyles.None, CultureInfo.InvariantCulture, out i))
266 if (long.TryParse (v, NumberStyles.None, CultureInfo.InvariantCulture, out l))
269 if (ulong.TryParse (v, NumberStyles.None, CultureInfo.InvariantCulture, out ul))
272 if (double.TryParse (v, NumberStyles.None, CultureInfo.InvariantCulture, out dbl))
275 if (decimal.TryParse (v, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out dec))
277 throw SerializationError (String.Format ("Invalid JSON input: {0}", v));
279 throw SerializationError (String.Format ("Unexpected type: {0}", type));
283 string FormatTypeName (Type type)
285 return type.Namespace == null ? type.Name : String.Format ("{0}:#{1}", type.Name, type.Namespace);
288 string ToRuntimeTypeName (string s)
290 int idx = s.IndexOf (":#", StringComparison.Ordinal);
291 return idx < 0 ? s : String.Concat (s.Substring (idx + 2), ".", s.Substring (0, idx));
294 Type GetCollectionElementType (Type type)
297 return type.GetElementType ();
299 var inter = type.GetInterface ("System.Collections.Generic.IEnumerable`1", false);
301 return inter.GetGenericArguments () [0];
303 if (typeof (IEnumerable).IsAssignableFrom (type))
304 // return typeof(object) for mere collection.
305 return typeof (object);
310 object DeserializeGenericCollection (Type collectionType, Type elementType, object collectionInstance)
312 reader.ReadStartElement ();
314 if (collectionType.IsInterface)
315 collectionType = typeof (List<>).MakeGenericType (elementType);
316 if (TypeMap.IsDictionary (collectionType)) {
317 if (collectionInstance == null)
318 collectionInstance = Activator.CreateInstance (collectionType);
320 var keyType = elementType.IsGenericType ? elementType.GetGenericArguments () [0] : typeof (object);
321 var valueType = elementType.IsGenericType ? elementType.GetGenericArguments () [1] : typeof (object);
322 MethodInfo add = collectionType.GetMethod ("Add", new Type [] { keyType, valueType });
324 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
325 if (!reader.IsStartElement ("item"))
326 throw SerializationError (String.Format ("Expected element 'item', but found '{0}' in namespace '{1}'", reader.LocalName, reader.NamespaceURI));
328 // reading a KeyValuePair in the form of <Key .../><Value .../>
330 reader.MoveToContent ();
331 object key = ReadObject (keyType);
332 reader.MoveToContent ();
333 object val = ReadObject (valueType);
335 add.Invoke (collectionInstance, new [] { key, val });
337 ret = collectionInstance;
338 } else if (typeof (IList).IsAssignableFrom (collectionType)) {
340 Type listType = collectionType.IsArray ? typeof (List<>).MakeGenericType (elementType) : null;
342 Type listType = collectionType.IsArray ? typeof (ArrayList) : null;
346 if (collectionInstance == null)
347 c = (IList) Activator.CreateInstance (listType ?? collectionType);
349 c = (IList) collectionInstance;
351 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
352 if (!reader.IsStartElement ("item"))
353 throw SerializationError (String.Format ("Expected element 'item', but found '{0}' in namespace '{1}'", reader.LocalName, reader.NamespaceURI));
354 Type et = elementType == typeof (object) || elementType.IsAbstract ? null : elementType;
355 object elem = ReadObject (et ?? typeof (object));
359 if (collectionType.IsArray) {
360 Array array = Array.CreateInstance (elementType, c.Count);
367 ret = collectionType.IsArray ? ((ArrayList) c).ToArray (elementType) : c;
370 if (collectionInstance == null)
371 collectionInstance = Activator.CreateInstance (collectionType);
374 if (collectionInstance.GetType ().IsGenericType &&
375 collectionInstance.GetType ().GetGenericTypeDefinition () == typeof (LinkedList<>))
376 add = collectionType.GetMethod ("AddLast", new Type [] { elementType });
378 add = collectionType.GetMethod ("Add", new Type [] { elementType });
381 var icoll = typeof (ICollection<>).MakeGenericType (elementType);
382 if (icoll.IsAssignableFrom (collectionInstance.GetType ()))
383 add = icoll.GetMethod ("Add");
386 throw new MissingMethodException (elementType.FullName, "Add");
388 for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
389 if (!reader.IsStartElement ("item"))
390 throw SerializationError (String.Format ("Expected element 'item', but found '{0}' in namespace '{1}'", reader.LocalName, reader.NamespaceURI));
391 object element = ReadObject (elementType);
392 add.Invoke (collectionInstance, new object [] { element });
394 ret = collectionInstance;
397 reader.ReadEndElement ();
401 TypeMap GetTypeMap (Type type)
404 if (!typemaps.TryGetValue (type, out map)) {
405 map = TypeMap.CreateTypeMap (type);
406 typemaps [type] = map;
411 Exception SerializationError (string basemsg)
413 IXmlLineInfo li = reader as IXmlLineInfo;
414 if (li == null || !li.HasLineInfo ())
415 return new SerializationException (basemsg);
417 return new SerializationException (String.Format ("{0}. Error at {1} ({2},{3})", basemsg, reader.BaseURI, li.LineNumber, li.LinePosition));