使用DataContractJsonSerializer将数组值反序列化为.NET属性

时间:2010-04-26 20:42:56

标签: c# json serialization

我正在使用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子集一起使用。如果解决方案也可以执行相反的序列化,那将是很好的。

2 个答案:

答案 0 :(得分:23)

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

在Silverlight上,这是一个简单而简单的解决方法。从imlpements Book的类型中导出ICollection<object>类。由于序列化JSON中的类型为object[],因此框架将尽职地将其序列化为您的ICollection<object>,然后您可以使用您的属性进行包装。

最简单(也是最讨厌的)只是来自List<object>。这种简单的黑客行为有一个缺点,即用户可以修改基础列表数据并弄乱您的属性。如果您是此代码的唯一用户,那可能没问题。通过更多的工作,您可以滚动自己的ICollection实现,并且只允许运行序列化的足够方法,并为其余部分抛出异常。我在下面列出了两种方法的代码示例。

如果上述黑客对你来说太难看了,我相信有更多优雅的方法可以解决这个问题。您可能希望将注意力集中在为List<Book>属性创建自定义集合类型而不是collectionitems。此类型可以包含类型为List<object[]>的字段(这是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次(在反序列化期间),但不允许通过{{1}进行修改}。使用显式接口实现公开ICollection方法,并且这些方法有一些属性可以将它们隐藏在智能感知中,这应该会进一步降低黑客因子。但是你可以看到这是更多的代码。

ICollection<T>

顺便说一下,我第一次阅读你的问题时,我跳过了重要的Silverlight要求。哎呀!无论如何,如果不使用Silverlight,这就是我为这种情况编写的解决方案 - 它更容易,我也可以将它保存在以后的任何Google员工中。

(正常的.NET框架,而不是Silverlight)你正在寻找的魔法是IDataContractSurrogate。如果要在序列化/反序列化时将一种类型替换为另一种类型,请实现此接口。在您的情况下,您想用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); } } } } 替换object[]

以下是一些显示其工作原理的代码:

Book

答案 1 :(得分:1)

我觉得你的问题非常有趣。所以我必须花时间试图解决问题。目前我收到了一个可以序列化和去除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下测试这种方法。