【问题标题】:Deserialize array values to .NET properties using DataContractJsonSerializer使用 DataContractJsonSerializer 将数组值反序列化为 .NET 属性
【发布时间】:2010-04-26 20:42:56
【问题描述】:

我正在使用 Silverlight 4 中的 DataContractJsonSerializer,并希望反序列化以下 JSON:

{
    "collectionname":"Books",
    "collectionitems": [
            ["12345-67890",201,
             "Book One"],
            ["09876-54321",45,
             "Book Two"]
        ]
}

进入如下类:

class BookCollection
{
  public string collectionname { get; set; }
  public List<Book> collectionitems { get; set; }
}

class Book
{
  public string Id { get; set; }
  public int NumberOfPages { get; set; }
  public string Title { get; set; }
}

扩展 DataContractJsonSerializer 以将“collectionitems”中未命名的第一个数组元素映射到 Book 类的 Id 属性、第二个元素映射到 NumberOfPages 属性和最后一个元素到 Title 的正确位置是什么?在这种情况下,我无法控制 JSON 的生成,并希望该解决方案能够与 .NET 的 Silverlight 子集一起使用。如果解决方案也可以执行反向序列化,那就太好了。

【问题讨论】:

    标签: c# json serialization


    【解决方案1】:

    如果这不是 Silverlight,您可以在序列化/反序列化时使用 IDataContractSurrogate 来使用 object[](您的 JSON 中实际存在的内容)而不是 Book。遗憾的是,IDataContractSurrogate(以及使用它的 DataContractJsonSerializer 构造函数的重载)在 Silverlight 中不可用。

    在 Silverlight 上,这里有一个 hacky 但简单的解决方法。从实现ICollection&lt;object&gt; 的类型派生Book 类。由于序列化 JSON 中的类型是 object[],因此框架将尽职尽责地将其序列化为您的 ICollection&lt;object&gt;,然后您可以使用您的属性进行包装。

    最简单(也是最难的)就是从List&lt;object&gt; 派生。这种简单的 hack 的缺点是用户可以修改基础列表数据并弄乱您的属性。如果您是此代码的唯一用户,那可能没问题。再做一些工作,您可以滚动您自己的 ICollection 实现,并只允许运行足够的方法以使序列化工作,并为其余的抛出异常。我在下面提供了两种方法的代码示例。

    如果上面的 hack 对你来说太难看了,我相信还有更优雅的方法来处理这个问题。您可能希望将注意力集中在为您的 collectionitems 属性创建自定义集合类型而不是 List&lt;Book&gt; 上。此类型可能包含一个类型为 List&lt;object[]&gt; 的字段(这是您的 JSON 中的实际类型),您可能能够说服序列化程序填充该字段。然后,您的 IList 实现可以将该数据挖掘到实际的 Book 实例中。

    另一行调查可以尝试强制转换。例如,您能否在Bookstring[] 之间实现隐式类型转换,并且序列化是否足够聪明以使用它?我对此表示怀疑,但可能值得一试。

    无论如何,这里是上面提到的从 ICollection 获取的代码示例。警告:我还没有在 Silverlight 上验证这些,但它们应该只使用 Silverlight 可访问的类型,所以我认为(手指交叉!)它应该可以正常工作。

    简单的黑客示例

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.Serialization.Json;
    using System.Runtime.Serialization;
    using System.IO;
    
    [DataContract]
    class BookCollection
    {
        [DataMember(Order=1)]
        public string collectionname { get; set; }
    
        [DataMember(Order = 2)]
        public List<Book> collectionitems { get; set; }
    }
    
    [CollectionDataContract]
    class Book : List<object>
    {
        public string Id { get { return (string)this[0]; } set { this[0] = value; } }
        public int NumberOfPages { get { return (int)this[1]; } set { this[1] = value; } }
        public string Title { get { return (string)this[2]; } set { this[2] = value; } }
    
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(BookCollection));
            string json = "{"
                        + "\"collectionname\":\"Books\","
                        + "\"collectionitems\": [ "
                                + "[\"12345-67890\",201,\"Book One\"],"
                                + "[\"09876-54321\",45,\"Book Two\"]"
                            + "]"
                        + "}";
    
            using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
            {
                BookCollection obj = ser.ReadObject(ms) as BookCollection;
                using (MemoryStream ms2 = new MemoryStream())
                {
                    ser.WriteObject(ms2, obj);
                    string serializedJson = Encoding.UTF8.GetString(ms2.GetBuffer(), 0,  (int)ms2.Length);
                }
            }
        }
    }
    

    更难、更简单的示例

    这是第二个示例,显示了 ICollection 的手动实现,它阻止用户访问集合 - 它支持调用 Add() 3 次(在反序列化期间),但不允许通过 ICollection&lt;T&gt; 进行修改。 ICollection 方法是使用显式接口实现公开的,并且这些方法上有一些属性可以将它们从智能感知中隐藏起来,这应该会进一步减少黑客因素。但是正如你所看到的,这是更多的代码。

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.Serialization.Json;
    using System.Runtime.Serialization;
    using System.IO;
    using System.Diagnostics;
    using System.ComponentModel;
    
    [DataContract]
    class BookCollection
    {
        [DataMember(Order=1)]
        public string collectionname { get; set; }
    
        [DataMember(Order = 2)]
        public List<Book> collectionitems { get; set; }
    }
    
    [CollectionDataContract]
    class Book : ICollection<object>
    {
        public string Id { get; set; }
        public int NumberOfPages { get; set; }
        public string Title { get; set; }
    
        // code below here is only used for serialization/deserialization
    
        // keeps track of how many properties have been initialized
        [EditorBrowsable(EditorBrowsableState.Never)]
        private int counter = 0;
    
        [EditorBrowsable(EditorBrowsableState.Never)]
        public void Add(object item)
        {
            switch (++counter)
            {
                case 1:
                    Id = (string)item;
                    break;
                case 2:
                    NumberOfPages = (int)item;
                    break;
                case 3:
                    Title = (string)item;
                    break;
                default:
                    throw new NotSupportedException();
            }
        }
    
        [EditorBrowsable(EditorBrowsableState.Never)]
        IEnumerator<object> System.Collections.Generic.IEnumerable<object>.GetEnumerator() 
        {
            return new List<object> { Id, NumberOfPages, Title }.GetEnumerator();
        }
    
        [EditorBrowsable(EditorBrowsableState.Never)]
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
        {
            return new object[] { Id, NumberOfPages, Title }.GetEnumerator();
        }
    
        [EditorBrowsable(EditorBrowsableState.Never)]
        int System.Collections.Generic.ICollection<object>.Count 
        { 
            get { return 3; } 
        }
    
        [EditorBrowsable(EditorBrowsableState.Never)]
        bool System.Collections.Generic.ICollection<object>.IsReadOnly 
        { get { throw new NotSupportedException(); } }
    
        [EditorBrowsable(EditorBrowsableState.Never)]
        void System.Collections.Generic.ICollection<object>.Clear() 
        { throw new NotSupportedException(); }
    
        [EditorBrowsable(EditorBrowsableState.Never)]
        bool System.Collections.Generic.ICollection<object>.Contains(object item) 
        { throw new NotSupportedException(); }
    
        [EditorBrowsable(EditorBrowsableState.Never)]
        void System.Collections.Generic.ICollection<object>.CopyTo(object[] array, int arrayIndex) 
        { throw new NotSupportedException(); }
    
        [EditorBrowsable(EditorBrowsableState.Never)]
        bool System.Collections.Generic.ICollection<object>.Remove(object item) 
        { throw new NotSupportedException(); }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(BookCollection));
            string json = "{"
                        + "\"collectionname\":\"Books\","
                        + "\"collectionitems\": [ "
                                + "[\"12345-67890\",201,\"Book One\"],"
                                + "[\"09876-54321\",45,\"Book Two\"]"
                            + "]"
                        + "}";
    
            using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
            {
                BookCollection obj = ser.ReadObject(ms) as BookCollection;
                using (MemoryStream ms2 = new MemoryStream())
                {
                    ser.WriteObject(ms2, obj);
                    string serializedJson = Encoding.UTF8.GetString(ms2.GetBuffer(), 0,  (int)ms2.Length);
                }
            }
        }
    }
    

    顺便说一句,我第一次阅读您的问题时,我跳过了重要的 Silverlight 要求。哎呀!无论如何,如果不使用 Silverlight,这是我为该案例编写的解决方案——它更容易,我不妨将它保存在这里以供以后的任何 Google 员工使用。

    您正在寻找的(在常规 .NET 框架上,而不是 Silverlight 上)魔法是 IDataContractSurrogate。当您想在序列化/反序列化时将一种类型替换为另一种类型时,请实现此接口。在您的情况下,您可以用object[] 替换Book

    这里有一些代码展示了它是如何工作的:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.Serialization.Json;
    using System.Runtime.Serialization;
    using System.IO;
    using System.Collections.ObjectModel;
    
    [DataContract]
    class BookCollection
    {
        [DataMember(Order=1)]
        public string collectionname { get; set; }
    
        [DataMember(Order = 2)]
        public List<Book> collectionitems { get; set; }
    }
    
    class Book 
    { 
      public string Id { get; set; } 
      public int NumberOfPages { get; set; } 
      public string Title { get; set; } 
    } 
    
    // A type surrogate substitutes object[] for Book when serializing/deserializing.
    class BookTypeSurrogate : IDataContractSurrogate
    {
        public Type GetDataContractType(Type type)
        {
            // "Book" will be serialized as an object array
            // This method is called during serialization, deserialization, and schema export. 
            if (typeof(Book).IsAssignableFrom(type))
            {
                return typeof(object[]);
            }
            return type;
        }
        public object GetObjectToSerialize(object obj, Type targetType)
        {
            // This method is called on serialization.
            if (obj is Book)
            {
                Book book = (Book) obj;
                return new object[] { book.Id, book.NumberOfPages, book.Title };
            }
            return obj;
        }
        public object GetDeserializedObject(object obj, Type targetType)
        {
            // This method is called on deserialization.
            if (obj is object[])
            {
                object[] arr = (object[])obj;
                Book book = new Book { Id = (string)arr[0], NumberOfPages = (int)arr[1], Title = (string)arr[2] };
                return book;
            }
            return obj;
        }
        public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
        {
            return null; // not used
        }
        public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
        {
            return typeDeclaration; // Not used
        }
        public object GetCustomDataToExport(Type clrType, Type dataContractType)
        {
            return null; // not used
        }
        public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
        {
            return null; // not used
        }
        public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
        {
            return; // not used
        }
    }
    
    
    class Program
    {
        static void Main(string[] args)
        {
            DataContractJsonSerializer ser  =
                new DataContractJsonSerializer(
                    typeof(BookCollection), 
                    new List<Type>(),        /* knownTypes */
                    int.MaxValue,            /* maxItemsInObjectGraph */ 
                    false,                   /* ignoreExtensionDataObject */
                    new BookTypeSurrogate(),  /* dataContractSurrogate */
                    false                    /* alwaysEmitTypeInformation */
                    );
            string json = "{"
                        + "\"collectionname\":\"Books\","
                        + "\"collectionitems\": [ "
                                + "[\"12345-67890\",201,\"Book One\"],"
                                + "[\"09876-54321\",45,\"Book Two\"]"
                            + "]"
                        + "}";
    
            using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
            {
                BookCollection obj = ser.ReadObject(ms) as BookCollection;
                using (MemoryStream ms2 = new MemoryStream())
                {
                    ser.WriteObject(ms2, obj);
                    string serializedJson = Encoding.UTF8.GetString(ms2.GetBuffer(), 0,  (int)ms2.Length);
                }
            }
        }
    }
    

    【讨论】:

    • 非常有趣的解决方案!我喜欢!只有两个小评论:1)应该将构造函数public Book () : base (3) { base.Add (null); base.Add (0); base.Add (null); return;} 添加到第一个解决方案的Book 类中,或者在setter 中接收ArgumentOutOfRangeException。 2) 有点遗憾,Book 类中的数据将被保存为无类型 List 并且强类型版本将仅用于序列化/反序列化。可以在第一个解决方案中更改吗?
    • +1:第二个版本非常优雅。这个例子现在在我的最爱列表中。
    • 嗨奥列格 - 感谢您的建议!不幸的是,List&lt;T&gt;(int) 构造函数没有设置限制,它设置了起始容量,所以没有什么可以阻止以后增加列表。但是请查看我的新代码示例,其中显示了手动 ICollection&lt;object&gt; 实现。它更清洁、更安全,但需要更多代码。
    • 优秀的解决方案,感谢您花时间展示多种选择!
    【解决方案2】:

    我觉得你的问题很有趣。所以我不得不花时间试图解决这个问题。目前我收到了一个可以序列化和去实化 JSON 数据的示例,如下所示:

    {
      "collectionname":"Books",
      "collectionitems":[
        {"book":["12345-67890",201,"Book One"]},
        {"book":["09876-54321",45,"Book Two"]}
      ]
    }
    

    一个小控制台应用的对应代码:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Security.Permissions;
    
    namespace DataContractJsonSerializer {
        [DataContract]
        class BookCollection {
            [DataMember (Order = 0)]
            public string collectionname { get; set; }
            [DataMember (Order = 1)]
            public List<Book> collectionitems { get; set; }
        }
    
        [Serializable]
        [KnownType (typeof (object[]))]
        class Book: ISerializable {
            public string Id { get; set; }
            public int NumberOfPages { get; set; }
            public string Title { get; set; }
    
            public Book () { }
    
            [SecurityPermissionAttribute (SecurityAction.Demand, Flags = SecurityPermissionFlag.SerializationFormatter)]
            protected Book (SerializationInfo info, StreamingContext context) {
                // called by DataContractJsonSerializer.ReadObject
                Object[] ar = (Object[]) info.GetValue ("book", typeof (object[]));
    
                this.Id = (string)ar[0];
                this.NumberOfPages = (int)ar[1];
                this.Title = (string)ar[2];
            }
    
            [SecurityPermission (SecurityAction.Demand, SerializationFormatter = true)]
            public void GetObjectData (SerializationInfo info, StreamingContext context) {
                // called by DataContractJsonSerializer.WriteObject
                object[] ar = new object[] { (object)this.Id, (object)this.NumberOfPages, (object)this.Title };
                info.AddValue ("book", ar);
            }
        }
    
        class Program {
            static readonly string testJSONdata = "{\"collectionname\":\"Books\",\"collectionitems\":[{\"book\":[\"12345-67890\",201,\"Book One\"]},{\"book\":[\"09876-54321\",45,\"Book Two\"]}]}";
    
            static void Main (string[] args) {
                BookCollection test = new BookCollection () {
                    collectionname = "Books",
                    collectionitems = new List<Book> {
                        new Book() { Id = "12345-67890", NumberOfPages = 201, Title = "Book One"},
                        new Book() { Id = "09876-54321", NumberOfPages = 45, Title = "Book Two"},
                    }
                };
    
                MemoryStream memoryStream = new MemoryStream ();
                System.Runtime.Serialization.Json.DataContractJsonSerializer ser =
                    new System.Runtime.Serialization.Json.DataContractJsonSerializer (typeof (BookCollection));
                memoryStream.Position = 0;
                ser.WriteObject (memoryStream, test);
    
                memoryStream.Flush();
                memoryStream.Position = 0;
                StreamReader sr = new StreamReader(memoryStream);
                string str = sr.ReadToEnd ();
                Console.WriteLine ("The result of custom serialization:");
                Console.WriteLine (str);
    
                if (String.Compare (testJSONdata, str, StringComparison.Ordinal) != 0) {
                    Console.WriteLine ("Error in serialization: unexpected results.");
                        return;
                }
    
                byte[] jsonDataAsBytes = System.Text.Encoding.GetEncoding ("iso-8859-1").GetBytes (testJSONdata);
                MemoryStream stream = new MemoryStream (jsonDataAsBytes);
                stream.Position = 0;
                BookCollection p2 = (BookCollection)ser.ReadObject (stream);
            }
        }
    }
    

    我还没有在 Silverlight 4 下测试过这种方法。

    【讨论】:

    • 不幸的是,[Serializable] 属性显然在 Silverlight 中不起作用。引用blogs.msdn.com/suwatch/archive/2009/01/21/…,“在 Silverlight 中,没有 ISerializable 接口”。
    • 另外,您的解决方案的另一个问题是 JSON 与 OP 的示例不匹配 - 并且 OP 说他无法更改传入的 JSON 格式,他坚持使用现有的格式。他的数组是匿名的(没有附加到每个数组的“书”名),因此即使在非 Silverlight .NET 中,您的序列化示例也不适用于他的 JSON。
    • 另外,[SecurityPermission (SecurityAction.Demand, SerializationFormatter = true 在 Silverlight 上不做任何事情,并被标记为 [已过时]。根据msdn.microsoft.com/en-us/library/…,“您可以在基于 Silverlight 的应用程序中使用此类,但不会产生任何效果。”
    猜你喜欢
    • 1970-01-01
    • 2013-08-13
    • 1970-01-01
    • 1970-01-01
    • 2011-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-19
    相关资源
    最近更新 更多