Newtonsoft Json.NET JsonConverter属性在反序列化时保留引用问题 [英] Newtonsoft Json.NET JsonConverter attribute preserve references issue when deserializing

查看:136
本文介绍了Newtonsoft Json.NET JsonConverter属性在反序列化时保留引用问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在项目模型中,我使用 JsonConverter 属性来帮助(反)序列化这些模型。

In the models of a project I am using a JsonConverter attribute to help with the (de)serialization of those models.

转换器当前如下所示:

public class CustomJsonConverter : Newtonsoft.Json.JsonConverter
{
    bool _canWrite = true;
    public override bool CanWrite
    {
        get { return _canWrite; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
        serializer.DefaultValueHandling = DefaultValueHandling.Ignore;
        serializer.NullValueHandling = NullValueHandling.Ignore;

        _canWrite = false;
        var jObject = JObject.FromObject(value, serializer);
        _canWrite = true;

        jObject.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        serializer.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
        if (reader.TokenType == JsonToken.StartObject)
        {
            existingValue = existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
            serializer.Populate(reader, existingValue);
            return existingValue;
        }
        else if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }
        else
        {
            throw new JsonSerializationException();
        }
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IModelBase).IsAssignableFrom(objectType);
    }
}

模型的基类如下所示:

The models have a base class that looks like this:

[JsonConverter(typeof(CustomJsonConverter))]
public abstract class ModelBase : IModelBase
{
    public string ID { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime ModifiedAt { get; set; }
}

ModelBase 类具有其类型也从 ModelBase 派生的属性。例如:

Derived classes of the ModelBase class have properties of which the type are also derived from ModelBase. For example:

public class CustomerModel : ModelBase
{
    public string Name { get; set; }
    public UserModel CreatedBy { get; set; }
    public UserModel ModifiedBy { get; set; }
}

public class UserModel : ModelBase
{
    public string Name { get; set; }
    public UserModel CreatedBy { get; set; }
    public UserModel ModifiedBy { get; set; }
}

我正在ASP.NET Web API 2应用程序中使用这些模型(服务器端)和C#应用程序(客户端)中。对于大多数API调用,将返回模型数组。序列化模型时,一切按预期进行。但是,在反序列化时,只有每个引用的第一个出现都填充有信息。

I am using these models in an ASP.NET Web API 2 application (server side) and in C# applications (client side). For most of the API calls, an array of models is returned. When serializing the models, things work as expected. However, when deserializing, only the first occurrence of every reference is filled with information.

[
    {
        "$id": "1",
        "Name": "Customer1",
        "CreatedBy": {
            "$id": "2",
            "ID": "1",
            "Name": "User1"
        },
        "ModifiedBy": {
            "$id": "3",
            "ID": "3",
            "Name": "User3"
        },
        "ID": "1",
        "CreatedAt": "2019-02-06T00:00:04",
        "ModifiedAt": "2019-02-06T00:20:12"
    },
    {
        "$id": "4",
        "Name": "Customer2",
        "CreatedBy": {
            "$ref": "2"
        },
        "ModifiedBy": {
            "$ref": "2"
        },
        "ID": "2",
        "CreatedAt": "2019-02-06T00:10:00",
        "ModifiedAt": "2019-02-06T00:10:00"
    }
]

当尝试反序列化由Web API返回的JSON对象时, CreatedBy CustomerModel 对象,c>和 ModifiedBy 属性是正确的。但是,对于第二个 CustomerModel 对象,这些属性将是未设置任何属性的新 UserModel 实例。

When trying to deserialize this JSON object returned by the web API, the CreatedBy and ModifiedBy properties will be correct for the first CustomerModel object. For the second CustomerModel object, however, those properties will be new UserModel instances without any properties set.

要反序列化JSON字符串,我使用以下代码:

To deserialize the JSON string, I am using the following code:

using (var sr = new StreamReader(streamFromWebAPICall))
{                
    using (var jtr = new JsonTextReader(sr))
    {
        var js = new JsonSerializer();
        return js.Deserialize(jtr, objectType);
    }
}

该如何设置所有反序列化的属性对象正确吗?

What can I do to set the properties on all deserialized objects correctly?

问题似乎出在序列化器中。填充(读者,现有值),其中的引用不被记住。

The problem seems to be in the serializer.Populate(reader, existingValue), where the references aren't remembered.

推荐答案

您的基本问题就是您提供了自定义 JsonConverter 表示您还希望为其启用 PreserveReferencesHandling 。但是无论何时应用自定义转换器,它都必须手动处理一切,其中包括解析和生成 $ ref $ id 属性。您的转换器不会执行此操作,因此反序列化代码无法正确地反序列化对象图。

Your basic problem is that you are supplying a custom JsonConverter for a type for which you also want to enable PreserveReferencesHandling. But whenever a custom converter is applied, it must take care of everything manually, including parsing and generating of "$ref" and "$id" properties. Your converter does not do this, hence your deserialization code does not deserialize your object graph correctly.

自定义对象序列化与PreserveReferencesHandling 的53716866/3744182>接受的答案模板转换器,显示如何处理这些属性。但是,由于您的转换器似乎只在(反)序列化之前切换了一些序列化程序设置,因此您可以简单地调用以递归方式对对象进行(反)序列化,从而在一段时间内禁用转换器,例如:

The accepted answer to Custom object serialization vs PreserveReferencesHandling includes a template converter that shows how these properties can be dealt with. However, since your converter only seems to be toggling some serializer settings before (de)serialization, you could simply make a call to recursively (de)serialize the object, disabling the converter for the duration, like so:

public class CustomJsonConverter : Newtonsoft.Json.JsonConverter
{
    [ThreadStatic]
    static bool disabled;

    // Disables the converter in a thread-safe manner.
    bool Disabled { get { return disabled; } set { disabled = value; } }

    public override bool CanWrite { get { return !Disabled; } }

    public override bool CanRead { get { return !Disabled; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        using (new PushValue<bool>(true, () => Disabled, val => Disabled = val))
        using (new PushValue<PreserveReferencesHandling>(PreserveReferencesHandling.Objects, () => serializer.PreserveReferencesHandling, val => serializer.PreserveReferencesHandling = val))
        using (new PushValue<DefaultValueHandling>(DefaultValueHandling.Ignore, () => serializer.DefaultValueHandling, val => serializer.DefaultValueHandling = val))
        using (new PushValue<NullValueHandling>(NullValueHandling.Ignore, () => serializer.NullValueHandling, val => serializer.NullValueHandling = val))
        {
            serializer.Serialize(writer, value);
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }
        using (new PushValue<bool>(true, () => Disabled, val => Disabled = val))
        using (new PushValue<PreserveReferencesHandling>(PreserveReferencesHandling.Objects, () => serializer.PreserveReferencesHandling, val => serializer.PreserveReferencesHandling = val))
        using (new PushValue<DefaultValueHandling>(DefaultValueHandling.Ignore, () => serializer.DefaultValueHandling, val => serializer.DefaultValueHandling = val))
        using (new PushValue<NullValueHandling>(NullValueHandling.Ignore, () => serializer.NullValueHandling, val => serializer.NullValueHandling = val))
        {
            return serializer.Deserialize(reader, objectType);
        }
    }

    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }
}

public struct PushValue<T> : IDisposable
{
    Action<T> setValue;
    T oldValue;

    public PushValue(T value, Func<T> getValue, Action<T> setValue)
    {
        if (getValue == null || setValue == null)
            throw new ArgumentNullException();
        this.setValue = setValue;
        this.oldValue = getValue();
        setValue(value);
    }

    // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
    public void Dispose()
    {
        if (setValue != null)
            setValue(oldValue);
    }
}

请注意,禁用转换器的逻辑必须是之所以成为线程安全的,是因为Json.NET将跨线程共享合约和转换器。

Note that the logic to disable the converter needs to be made thread-safe since Json.NET will share contracts and converters across threads.

演示小提琴#1 此处

作为替代方案,您可以完全消除转换器并应用 [JsonObject(IsReference = true)] 直接添加到 ModelBase

As an alternative, you could completely eliminate the converter and apply [JsonObject(IsReference = true)] directly to ModelBase:

[JsonObject(IsReference = true)]
public abstract class ModelBase : IModelBase
{
    public string ID { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime ModifiedAt { get; set; }
}

然后使用 DefaultValueHandling.Ignore进行序列化和反序列化/ code>和 NullValueHandling.Ignore 在如下设置中指定:

Then serialize and deserialize with DefaultValueHandling.Ignore and NullValueHandling.Ignore specified in settings like so:

static object Deserialize(Stream streamFromWebAPICall, Type objectType)
{
    using (var sr = new StreamReader(streamFromWebAPICall))
    {
        using (var jtr = new JsonTextReader(sr))
        {
            var settings = new JsonSerializerSettings
            {
                DefaultValueHandling = DefaultValueHandling.Ignore,
                NullValueHandling = NullValueHandling.Ignore,
            };
            var js = JsonSerializer.CreateDefault(settings);
            return js.Deserialize(jtr, objectType);
        }
    }
}

演示小提琴#2 此处

请注意,您也可以设置 [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 以及 Json.NET 11.0.1 >,但在 JsonObjectAttribute 上似乎没有 ItemDefaultValueHandling 设置,因此将其添加到序列化程序设置中(或使用可选的值类型值(例如 CreatedAt )必须为nullable。

Note you can also set [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] as of Json.NET 11.0.1, but there does not appear to be an ItemDefaultValueHandling setting on JsonObjectAttribute, so adding that to serializer settings (or using nullables for optional value-type values such as CreatedAt) is required.

这篇关于Newtonsoft Json.NET JsonConverter属性在反序列化时保留引用问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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