| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- //
- // JsonSerializationWriter.cs
- //
- // Author:
- // Atsushi Enomoto <[email protected]>
- //
- // Copyright (C) 2008 Novell, Inc (http://www.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.Collections.ObjectModel;
- using System.Globalization;
- using System.IO;
- using System.Reflection;
- using System.Text;
- using System.Xml;
- namespace System.Runtime.Serialization.Json
- {
- class JsonSerializationWriter
- {
- DataContractJsonSerializer serializer;
- XmlWriter writer;
- int serialized_object_count;
- bool always_emit_type;
- Dictionary<Type, TypeMap> typemaps = new Dictionary<Type, TypeMap> ();
- Type root_type;
- public JsonSerializationWriter (DataContractJsonSerializer serializer, XmlWriter writer, Type rootType, bool alwaysEmitTypeInformation)
- {
- this.serializer = serializer;
- this.writer = writer;
- this.root_type = rootType;
- this.always_emit_type = alwaysEmitTypeInformation;
- }
- public XmlWriter Writer {
- get { return writer; }
- }
- public void WriteObjectContent (object graph, bool top, bool outputTypeName)
- {
- if (graph == null) {
- if (top)
- GetTypeMap (root_type); // to make sure to reject invalid contracts
- writer.WriteAttributeString ("type", "null");
- writer.WriteString (null);
- return;
- }
- if (serialized_object_count ++ == serializer.MaxItemsInObjectGraph)
- throw new SerializationException (String.Format ("The object graph exceeded the maximum object count '{0}' specified in the serializer", serializer.MaxItemsInObjectGraph));
- var type = graph.GetType ();
- if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Nullable<>))
- type = type.GetGenericArguments () [0];
- switch (Type.GetTypeCode (type)) {
- case TypeCode.Char:
- case TypeCode.String:
- writer.WriteString (graph.ToString ());
- break;
- case TypeCode.Single:
- case TypeCode.Double:
- writer.WriteAttributeString ("type", "number");
- writer.WriteString (((IFormattable) graph).ToString ("R", CultureInfo.InvariantCulture));
- break;
- case TypeCode.Byte:
- case TypeCode.SByte:
- case TypeCode.Int16:
- case TypeCode.Int32:
- case TypeCode.Int64:
- case TypeCode.UInt16:
- case TypeCode.UInt32:
- case TypeCode.UInt64:
- case TypeCode.Decimal:
- writer.WriteAttributeString ("type", "number");
- if (type.IsEnum)
- graph = ((IConvertible) graph).ToType (Enum.GetUnderlyingType (type), CultureInfo.InvariantCulture);
- writer.WriteString (((IFormattable) graph).ToString ("G", CultureInfo.InvariantCulture));
- break;
- case TypeCode.Boolean:
- writer.WriteAttributeString ("type", "boolean");
- if ((bool) graph)
- writer.WriteString ("true");
- else
- writer.WriteString ("false");
- break;
- case TypeCode.DateTime:
- writer.WriteString (String.Format (CultureInfo.InvariantCulture, "/Date({0})/", (long) ((DateTime) graph).Subtract (new DateTime (1970, 1, 1)).TotalMilliseconds));
- break;
- default:
- if (graph is Guid) {
- goto case TypeCode.String;
- } else if (graph is Uri) {
- goto case TypeCode.String;
- } else if (graph is XmlQualifiedName) {
- XmlQualifiedName qn = (XmlQualifiedName) graph;
- writer.WriteString (qn.Name);
- writer.WriteString (":");
- writer.WriteString (qn.Namespace);
- } else if (TypeMap.IsDictionary (type)) {
- writer.WriteAttributeString ("type", "array");
- bool otn = !(graph is Array && type.GetElementType () != typeof (object));
- var d = graph as IDictionary;
- if (d != null) {
- // Optimize the IDictionary case to avoid reflection
- foreach (object k in d.Keys)
- WriteItem (k, d [k], otn);
- } else {
- // we can't typecast to IDictionary<,> and can't use dynamic for iOS support
- var itemGetter = GetDictionaryProperty (type, "Item");
- var keysGetter = GetDictionaryProperty (type, "Keys");
- var argarr = new object [1];
- foreach (object o in (IEnumerable) keysGetter.GetValue (graph, null)) {
- argarr [0] = o;
- WriteItem (o, itemGetter.GetValue (graph, argarr), otn);
- }
- }
- } else if (graph is Array || TypeMap.IsEnumerable (type)) {
- writer.WriteAttributeString ("type", "array");
- foreach (object o in (IEnumerable) graph) {
- writer.WriteStartElement ("item");
- // when it is typed, then no need to output "__type"
- WriteObjectContent (o, false, !(graph is Array && type.GetElementType () != typeof (object)));
- writer.WriteEndElement ();
- }
- } else { // object
- TypeMap tm = GetTypeMap (type);
- if (tm != null) {
- // FIXME: I'm not sure how it is determined whether __type is written or not...
- if (outputTypeName || always_emit_type)
- writer.WriteAttributeString ("__type", FormatTypeName (type));
- tm.Serialize (this, graph, "object");
- }
- else
- // it does not emit type="object" (as the graph is regarded as a string)
- // writer.WriteString (graph.ToString ());
- throw new InvalidDataContractException (String.Format ("Type {0} cannot be serialized by this JSON serializer", type));
- }
- break;
- }
- }
- void WriteItem (object key, object value, bool outputTypeName)
- {
- writer.WriteStartElement ("item");
- writer.WriteAttributeString ("type", "object");
- // outputting a KeyValuePair as <Key .. /><Value ... />
- writer.WriteStartElement ("Key");
- WriteObjectContent (key, false, outputTypeName);
- writer.WriteEndElement ();
- writer.WriteStartElement ("Value");
- WriteObjectContent (value, false, outputTypeName);
- writer.WriteEndElement ();
- writer.WriteEndElement ();
- }
- PropertyInfo GetDictionaryProperty (Type type, string propertyName)
- {
- var p = type.GetProperty (propertyName);
- if (p != null)
- return p;
- // check explicit - but the generic names might differ, e.g. TKey,TValue vs T,V
- var ap = type.GetProperties (BindingFlags.Instance | BindingFlags.NonPublic);
- foreach (var cp in ap) {
- if (!cp.Name.EndsWith (propertyName, StringComparison.Ordinal))
- continue;
- if (cp.Name.StartsWith ("System.Collections.Generic.IDictionary<", StringComparison.Ordinal))
- return cp;
- }
- return null;
- }
- string FormatTypeName (Type type)
- {
- return String.Format ("{0}:#{1}", type.Name, type.Namespace);
- }
- TypeMap GetTypeMap (Type type)
- {
- TypeMap map;
- if (!typemaps.TryGetValue (type, out map)) {
- map = TypeMap.CreateTypeMap (type);
- typemaps [type] = map;
- }
- return map;
- }
- }
- }
|