从 JSON 反序列化混合对象列表

发布于 2024-11-09 15:26:23 字数 2020 浏览 0 评论 0原文

我使用 DataContractJsonSerializer 从外部服务反序列化对象。在大多数情况下,这对我来说非常有效。但是,在一种情况下,我需要反序列化 JSON,其中包含全部继承自同一基类的对象列表,但该列表中有许多不同类型的对象。

我知道可以通过在序列化器的构造函数中包含已知类型的列表来轻松完成此操作,但我无权访问生成此 JSON 服务的代码。我使用的类型将与服务中使用的类型不同(主要是类名和命名空间不同)。换句话说,用于序列化数据的类将与我用于反序列化它的类不同,即使它们非常相似。

使用 XML DataContractSerializer,我可以传入 DataContractResolver 将服务类型映射到我自己的类型,但 DataContractJsonSerializer 没有这样的构造函数>。 有什么方法可以做到这一点吗?我能找到的唯一选择是:编写自己的反序列化器,或使用 Microsoft 的 JsonObject 未经测试,“不应在生产环境中使用”。

这是一个例子:

[DataContract]
public class Person
{
    [DataMember]
    public string Name { get; set; }
}

[DataContract]
public class Student : Person
{
    [DataMember]
    public int StudentId { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var jsonStr = "[{\"__type\":\"Student:#UnknownProject\",\"Name\":\"John Smith\",\"StudentId\":1},{\"Name\":\"James Adams\"}]";

        using (var stream = new MemoryStream())
        {
            var writer = new StreamWriter(stream);
            writer.Write(jsonStr);
            writer.Flush();

            stream.Position = 0;
            var s = new DataContractJsonSerializer(typeof(List<Person>), new Type[] { typeof(Student), typeof(Person) });
            // Crashes on this line with the error below
            var personList = (List<Person>)s.ReadObject(stream);
        }
    }
}

这是上面评论中提到的错误:

Element ':item' contains data from a type that maps to the name
'http://schemas.datacontract.org/2004/07/UnknownProject:Student'. The
deserializer has no knowledge of any type that maps to this name. Consider using
a DataContractResolver or add the type corresponding to 'Student' to the list of
known types - for example, by using the KnownTypeAttribute attribute or by adding
it to the list of known types passed to DataContractSerializer.

I'm using the DataContractJsonSerializer to deserialize objects from an external service. In most cases, this has worked great for me. However, there is one case where I need to deserialize JSON that contains a list of objects that all inherit from the same base class, but there are many different types of objects in that list.

I know that it can be done easily by including a list of known types in the serializer's constructor, but I don't have access to the code that generated this JSON service. The types that I'm using will be different from the types used in the service (mostly just the class name and namespace will be different). In other words, the classes that the data was serialized with will not be the same classes that I'll use to deserialize it even though they'll be very similar.

With the XML DataContractSerializer, I can pass in a DataContractResolver to map the services types to my own types, but there is no such constructor for the DataContractJsonSerializer. Is there any way to do this? The only options that I've been able to find are: write my own deserializer, or use Microsoft's JsonObject that isn't tested and "should not be used in production environments."

Here is an example:

[DataContract]
public class Person
{
    [DataMember]
    public string Name { get; set; }
}

[DataContract]
public class Student : Person
{
    [DataMember]
    public int StudentId { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var jsonStr = "[{\"__type\":\"Student:#UnknownProject\",\"Name\":\"John Smith\",\"StudentId\":1},{\"Name\":\"James Adams\"}]";

        using (var stream = new MemoryStream())
        {
            var writer = new StreamWriter(stream);
            writer.Write(jsonStr);
            writer.Flush();

            stream.Position = 0;
            var s = new DataContractJsonSerializer(typeof(List<Person>), new Type[] { typeof(Student), typeof(Person) });
            // Crashes on this line with the error below
            var personList = (List<Person>)s.ReadObject(stream);
        }
    }
}

Here is the error mentioned in the comment above:

Element ':item' contains data from a type that maps to the name
'http://schemas.datacontract.org/2004/07/UnknownProject:Student'. The
deserializer has no knowledge of any type that maps to this name. Consider using
a DataContractResolver or add the type corresponding to 'Student' to the list of
known types - for example, by using the KnownTypeAttribute attribute or by adding
it to the list of known types passed to DataContractSerializer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

神妖 2024-11-16 15:26:23

我找到了答案。很简单。我只需要更新我的 DataContract 属性来指定它们在源 JSON 中映射到的命名空间(您也可以指定不同的名称),如下所示:

[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/UnknownProject")]
public class Person
{
    [DataMember]
    public string Name { get; set; }
}

[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/UnknownProject"]
public class Student : Person
{
    [DataMember]
    public int StudentId { get; set; }
}

I found the answer. It was very simple. I just needed to update my DataContract attribute to specify which namespace (you can also specify a different name) they map to in the source JSON like this:

[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/UnknownProject")]
public class Person
{
    [DataMember]
    public string Name { get; set; }
}

[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/UnknownProject"]
public class Student : Person
{
    [DataMember]
    public int StudentId { get; set; }
}
走走停停 2024-11-16 15:26:23

该 JsonObject 是 .NET 3.5 的示例。 codeplex 中有一个项目 - http://wcf.codeplex.com - 它具有经过测试的 JsonValue 实现/JsonObject/JsonArray/JsonPrimitive 类,包括源代码和单元测试。这样你就可以解析“无类型”JSON。另一个常用的 JSON 框架是 JSON.NET,位于 http://json.codeplex.com

That JsonObject was a sample for .NET 3.5. There is a project in codeplex - http://wcf.codeplex.com - which has a tested implementation of the JsonValue/JsonObject/JsonArray/JsonPrimitive classes, including source code and unit tests. With that you can parse "untyped" JSON. Another well-used JSON framework is the JSON.NET at http://json.codeplex.com.

美煞众生 2024-11-16 15:26:23

您可以在序列化之前创建 DTO。

我使用如下类:(伪代码)

class JsonDto

string Content {get;set;}
string Type {get;set;}

ctor(object) => sets Content & Type Properties

static JsonDto FromJson(string) // => Reads a Serialized JsonDto 
                                //    and sets Content+Type Properties

string ToJson() // => serializes itself into a json string

object Deserialize() // => deserializes the wrapped object to its saved Type
                     //    using Content+Type properties

T Deserialize<T>()   // deserializes the object as above and tries to cast to T

使用 JsonDto 您可以轻松地将任意对象序列化为 JSON 并将它们反序列化为其公共基本类型,因为反序列化器将始终知道原始类型并返回类型如果您使用通用 Deserialize 方法,将强制转换对象引用。

需要注意的是:如果设置 Type 属性,则应使用该类型的 AssemblyQualifiedName,但不使用版本属性(例如:MyCompany.SomeNamespace.MyType、MyCompany.SomeAssembly) 。如果您只使用 Type 类的 AssemblyQualifiedName 属性,那么如果您的程序集版本发生更改,您最终会遇到错误。

我以相同的方式实现了 JsonDtoCollection,它派生自 List 并提供了处理对象集合的方法。

class JsonDtoCollection : List<JsonDto>

ctor(List<T>) => wraps all items of the list and adds them to itself

static JsonDtoCollection FromJson(string) // => Reads a collection of serialized
                                          //    JsonDtos and deserializes them, 
                                          //    returning a Collection

string ToJson() // => serializes itself into a json string

List<object> Deserialize() // => deserializes the wrapped objects using
                           //    JsonDto.Deserialize

List<T> Deserialize<T>()   // deserializes the as above and tries to cast to T

You may create a DTO before serializing.

I use a class like: (pseudo code)

class JsonDto

string Content {get;set;}
string Type {get;set;}

ctor(object) => sets Content & Type Properties

static JsonDto FromJson(string) // => Reads a Serialized JsonDto 
                                //    and sets Content+Type Properties

string ToJson() // => serializes itself into a json string

object Deserialize() // => deserializes the wrapped object to its saved Type
                     //    using Content+Type properties

T Deserialize<T>()   // deserializes the object as above and tries to cast to T

Using the JsonDto you can easily serialize arbitrary objects to JSON and deserialize them to their common base type because the deserializer will always know the original type and returns an type of object reference which will be casted if you use the generic Deserialize<T> method.

One caveat: If you set the Type property you should use the AssemblyQualifiedName of the type, however without the version attribute (ex: MyCompany.SomeNamespace.MyType, MyCompany.SomeAssembly). If you just use the AssemblyQualifiedName property of the Type class you will end up with errors if your assembly version changes.

I implemented a JsonDtoCollection the same way, which derives from List<JsonDto> and provides methods to handle collections of objects.

class JsonDtoCollection : List<JsonDto>

ctor(List<T>) => wraps all items of the list and adds them to itself

static JsonDtoCollection FromJson(string) // => Reads a collection of serialized
                                          //    JsonDtos and deserializes them, 
                                          //    returning a Collection

string ToJson() // => serializes itself into a json string

List<object> Deserialize() // => deserializes the wrapped objects using
                           //    JsonDto.Deserialize

List<T> Deserialize<T>()   // deserializes the as above and tries to cast to T
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文