使用DataContractJsonSerializer将数组值反序列化为.NET属性 [英] Deserialize array values to .NET properties using DataContractJsonSerializer

查看:101
本文介绍了使用DataContractJsonSerializer将数组值反序列化为.NET属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm working with the DataContractJsonSerializer in Silverlight 4 and would like to deserialize the following 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子集一起使用.如果该解决方案也可以执行相反的序列化,那就太好了.

What's the proper place to extend DataContractJsonSerializer to map the unnamed first array element in "collectionitems" to the Id property of the Book class, the second element to the NumberOfPages property and the final element to Title? I don't have control over the JSON generation in this instance and would like the solution to work with the Silverlight subset of .NET. It would be great if the solution could perform the reverse for serialization as well.

推荐答案

如果这不是Silverlight,则可以使用 IDataContractSurrogate (以及重载 DataContractJsonSerializer 构造函数的使用它)在Silverlight中不可用.

If this weren't Silverlight, you could use IDataContractSurrogate to use object[] (what's actually present in your JSON) instead of Book when serializing/deserializing. Sadly, IDataContractSurrogate (and the overloads of the DataContractJsonSerializer constructor which use it) aren't available in Silverlight.

在Silverlight上,这是一个骇人但简单的解决方法.从植入ICollection<object>的类型派生Book类.由于序列化JSON中的类型为object[],因此框架会尽职地将其序列化为ICollection<object>,您可以依次包装属性.

On Silverlight, here's a hacky but simple workaround. Derive the Book class from a type which imlpements ICollection<object>. Since the type in your serialized JSON is object[], the framework will dutifully serialize it into your ICollection<object>, which in turn you can wrap with your properties.

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

The easiest (and hackiest) is just to derive from List<object>. This easy hack has the downside that users can modify the underlying list data and mess up your properties. If you're the only user of this code, that might be OK. With a little more work, you can roll your own implementation of ICollection and permit only enough methods to run for serialization to work, and throwing exceptions for the rest. I included code samples for both approaches below.

如果上述技巧对您来说太丑陋,我敢肯定还有更多优雅的方法可以解决此问题.您可能希望将注意力集中在为collectionitems属性创建自定义集合类型而不是List<Book>上.此类型可以包含类型为List<object[]>的字段(这是JSON中的实际类型),您可以说服序列化程序进行填充.然后,您的IList实现可以将该数据挖掘到实际的Book实例中.

If the above hacks are too ugly for you, I'm sure there are more graceful ways to handle this. You'd probably want to focus your attention on creating a custom collection type instead of List<Book> for your collectionitems property. This type could contain a field of type List<object[]> (which is the actual type in your JSON) which you might be able to convince the serializer to populate. Then your IList implementation could mine that data into actual Book instances.

另一研究路线可以尝试强制转换,例如您可以在Bookstring[]之间实现隐式类型转换,并且序列化足够聪明来使用它吗?我对此表示怀疑,但值得一试.

Another line of investigation could try casting.For example could you implement an implicit type conversion between Book and string[] and would serialization be smart enough to use it? I doubt it, but it may be worth a try.

无论如何,这里是上面提到的从ICollection派生的代码示例.警告:我还没有在Silverlight上验证过这些,但是它们应该只使用Silverlight可访问的类型,因此我认为(手指交叉!)应该可以.

Anyway, here's code samples for the derive-from-ICollection hacks noted above. Caveat: I haven't verified these on Silverlight, but they should be using only Silverlight-accessible types so I think (fingers crossed!) it should work OK.

简单易用的样本

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<T>进行修改. ICollection方法是使用显式接口实现公开的,并且这些方法上有一些属性可将其隐藏于智能感知之外,这将进一步减少黑客因素.但是正如您所看到的,这是更多的代码.

Here's the second sample, showing a manual implementation of ICollection, which prevents users from accessing the collection-- it supports calling Add() 3 times (during deserialization) but otherwise doesn't allow modification via ICollection<T>. The ICollection methods are exposed using explicit interface implementation and there are attributes on those methods to hide them from intellisense, which should further reduce the hack factor. But as you can see this is a lot more code.

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员工使用.


BTW, the first time I read your quesiton I skipped over the important Silverlight requirement. Oops! Anyway, if not using Silverlight, here's the solution I coded up for that case-- it's much easier and I might as well save it here for any Googlers coming later.

您正在寻找的(在常规.NET框架上,而不是Silverlight上)魔术是

The (on regular .NET framework, not Silverlight) magic you're looking for is IDataContractSurrogate. Implement this interface when you want to substitute one type for another type when serializing/deserializing. In your case you wnat to substitute object[] for Book.

以下代码显示了其工作原理:

Here's some code showing how this works:

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);
            }
        }
    }
}

这篇关于使用DataContractJsonSerializer将数组值反序列化为.NET属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆