Newtonsoft Json.NET中使用JsonConverter属性反序列化时保留引用的问题

3
在我正在使用的项目模型中,我使用了一个JsonConverter属性来帮助对这些模型进行(反)序列化。当前转换器的代码如下:
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);
    }
}

模型有一个基类,看起来像这样:

[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的属性。例如:

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调用,都会返回一组模型。在序列化模型时,一切都按预期工作。但是,在反序列化时,只有每个引用的第一个出现会填充信息。

例如:

[
    {
        "$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对象时,对于第一个CustomerModel对象,CreatedBy和ModifiedBy属性将是正确的。然而,对于第二个CustomerModel对象,这些属性将是新的UserModel实例,没有设置任何属性。
为了反序列化JSON字符串,我正在使用以下代码:
using (var sr = new StreamReader(streamFromWebAPICall))
{                
    using (var jtr = new JsonTextReader(sr))
    {
        var js = new JsonSerializer();
        return js.Deserialize(jtr, objectType);
    }
}

我该如何正确设置所有反序列化对象的属性?
编辑:
问题似乎出现在serializer.Populate(reader, existingValue)中,引用没有被记住。

我还没有调试你的代码,但是你可能需要在转换器内手动检查$ref$id属性,就像自定义对象序列化 vs PreserveReferencesHandling中所示。 - dbc
此外,您能否解释一下您在 CustomJsonConverter 转换器中尝试做什么?是为了递归地为给定类型及其所有后代对象启用特定的序列化器选项吗? - dbc
我确实只是想为 Web API 返回的每个序列化的模型应用特定的序列化器设置。 - DylanVB
1个回答

4
你的基本问题是为一个类型提供了一个自定义JsonConverter,同时也想启用PreserveReferencesHandling。但是每当应用自定义转换器时,它必须手动处理所有内容,包括解析和生成"$ref""$id"属性。你的转换器没有这样做,因此你的反序列化代码无法正确地反序列化对象图。 接受答案中的自定义对象序列化与保留引用处理包含一个模板转换器,展示了如何处理这些属性。然而,由于你的转换器似乎只是在(反)序列化之前切换一些序列化器设置,你可以简单地调用递归地(反)序列化对象,并在此期间禁用转换器,像这样:
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将在线程之间共享契约和转换器。
演示fiddle #1 这里作为替代方案,您可以完全消除转换器,并直接应用[JsonObject(IsReference = true)]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.IgnoreNullValueHandling.Ignore来序列化和反序列化,就像这样:

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

演示fiddle #2 这里

请注意,自Json.NET 11.0.1起,您还可以设置[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)],但似乎在JsonObjectAttribute上没有ItemDefaultValueHandling设置,因此需要将其添加到序列化器设置中(或使用可选值类型值的可空类型,例如CreatedAt)。


你的自定义 JsonConverter 版本确实解决了我的问题。另一种方法并没有真正帮助到我,因为您是在客户端添加序列化器设置,而不是在 Web API 中添加。在发表原始文章后,我还发现了 [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 属性,但像您所说的那样,这种方法无法设置 ItemDefaultValueHandling。最终,我决定使用我发现的另一种方法。也就是,在我的 Web API 的 Global.asax.cs 文件的 Application_Start() 方法中应用 SerializerSettings. - DylanVB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接