如何覆盖指定在属性中的默认 Json 转换器

3
我希望以下的Author类型能够具有默认的JsonConverter,并且能够在运行时进行覆盖。
[JsonConverter(typeof(BaseJsonConverter))]
public class Author
{
    // The ID of an author entity in the application.
    public int ID { set; get; }

    // The ID of an Author entity in its source.
    public string SourceID { set; set; }
}

我使用了以下代码来覆盖默认转换器(即BaseJsonConverter)。
public class AlternativeConverter : BaseJsonConverter
{ // the serializer implementation is removed for clarity. }

// Deserialize using AlternativeConverter:
var author = JsonConvert.DeserializeObject<Author>(jsonString, new AlternativeConverter());

问题

使用上述调用,首先构造了AlternativeConverter;然而,随后初始化并使用BaseJsonConverter的实例进行反序列化。因此,AlternativeConverter未被使用。

可执行示例:https://dotnetfiddle.net/l0bgYO


用例

该应用程序旨在将从不同来源获取的不同JSON对象转换为通用的C#类型。通常数据来自我们定义默认转换器(即BaseJsonConverter)的源,而对于来自其他源的数据,我们为每个源定义不同的转换器。


背景

我知道像这篇文章中的方法,确实我部分地使用了类似的方法。关于那篇文章,我需要根据输入源具有不同的_propertyMappings,因为在我的应用程序中,属性到属性映射不是一对一的。例如,我有以下JSON对象:

{
   "id":123
}

// and

{
   "id":"456"
}

第一个JSON对象应该反序列化为:

author.ID = 123
author.SourceID = null

第二个JSON对象应该被反序列化为:

author.ID = 0
author.SourceID = "456"

据我所见,你的 JSON 键是相同的,只有值不同。为什么不尝试将 JSON 字符串解析为 JToken 作为你需要的 C# 对象呢?除非我没有完全理解你的问题,否则似乎你正在让自己变得困难。 - Jabberwocky
仅有一个JSON对象并不足以决定哪个Json属性应映射到哪个属性。因此,在输入源上下文中进行反序列化是必要的,即为来自不同来源的json具有不同的转换器。 - Dr. Strangelove
实际上是这样的。JToken是动态的。您已经有一个名为Author的对象。如果您知道传入的json是Author类型,请在每个变量的顶部添加[JsonProperty("blabla")]并将其解析为JToken,C#将完成其余工作。再次,如果我误解了重点,请告诉我。 - Jabberwocky
谢谢你的建议,但是我想你可能误解了我的意思。如果我按照你的建议去做(虽然我现在正在尝试避免它),那么有多个属性都会有[JsonProperty("blabal")]。比如说,IDSourceID都将有[JsonProperty("id")],请参考Json示例。 - Dr. Strangelove
啊,现在我明白你想做什么了。根据类型,你想将JSON中传入的“id”附加到Author的一个属性上。如果它是整数,就附加到整数上。明白了。 - Jabberwocky
不,我想你误解了。我必须对来自谷歌的数据进行反序列化,这与来自雅虎的数据不同! - Dr. Strangelove
2个回答

1
您可以使用自定义的ContractResolver来编程地覆盖[JsonConverter]属性。为了解决您的问题,您可以创建一个如下的自定义解析器:
public class CustomResolver : DefaultContractResolver
{
    private Dictionary<Type, JsonConverter> Converters { get; set; }

    public CustomResolver(Dictionary<Type, JsonConverter> converters)
    {
        Converters = converters;
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract = base.CreateObjectContract(objectType);
        if (Converters.TryGetValue(objectType, out JsonConverter converter))
        {
            contract.Converter = converter;
        }
        return contract;
    }
}

然后,当您想要在BaseJsonConverter的位置使用AlternativeConverter时,您可以像这样使用自定义解析器:

// map the `Author` type to the `AlternativeConverter`
var converters = new Dictionary<Type, JsonConverter>()
{
    { typeof(Author), new AlternativeConverter() }
};

// Create a resolver with the converter mapping and add it to the serializer settings
var settings = new JsonSerializerSettings
{
    ContractResolver = new CustomResolver(converters)
};

// Use the settings when deserializing
var author = JsonConvert.DeserializeObject<Author>(jsonString, settings);

演示 Fiddle: https://dotnetfiddle.net/cu0igV


当然,如果你只是将属性映射到不同的名称,那么你可以一开始就使用ContractResolver来完成这个工作,完全摆脱转换器。有关该方法的更多信息,请参见Json.NET deserialize or serialize json string and map properties to different property names defined at runtime

0

我认为你应该尝试使用不同的JsonSerializerSettings实例来处理不同的数据源,每个实例可以有不同的Converters集合。并且从你的类中移除JsonConverter属性。


我无法删除JsonConvert属性,因为它对于默认类型的(反)序列化是必不可少的。 - Dr. Strangelove
此外,我正在尝试使用类似于“转换器”的东西,但是通过“JsonConverter”属性设置的默认转换器会覆盖我传递的任何序列化程序。 - Dr. Strangelove

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