在Json.NET中仅针对一个属性的自定义序列化器,而无需更改模型类

时间:2018-12-13 18:27:06

标签: c# serialization json.net

我需要执行以下操作,但是我需要在不将属性放在模型类上或污染模型类的情况下进行操作。理想的解决方案将通过JsonSerializerSettings工作,而不会干扰其他自定义序列化。顺便说一句,以下内容来自这个问题:Custom conversion of specific objects in JSON.NET

public class Person
{
    public string FirstName { get; set; }
    [JsonConverter(typeof(AllCapsConverter))]
    public string LastName { get; set; }
    // more properties here in the real example, some of which nest to properties that use their own JsonConverters.
}

此玩具示例的JsonConverter(内容并非真正相关;相关的是我将其用于属性):

public class AllCapsConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
        => objectType == typeof(string);

    public override bool CanRead => false;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotSupportedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var str = value as string;
        var upper = str.ToUpperInvariant();
        JToken j = JToken.FromObject(upper);
        j.WriteTo(writer);
    }
}

通过单元测试:

public class PersonSerializationTest
{
    [Fact]
    public void SerializePerson_LastNameCaps()
    {
        var person = new Person
        {
            FirstName = "George",
            LastName = "Washington"
        };
        var serialized = JsonConvert.SerializeObject(person);
        var expected = @"{""FirstName"":""George"",""LastName"":""WASHINGTON""}";
        Assert.Equal(expected, serialized);
    }
}

2 个答案:

答案 0 :(得分:1)

您可以使用继承自DefaultContractResolver的{​​{3}}将转换器应用于特定属性。

首先,将ConfigurableContractResolvercustom IContractResolver抓到 this answer

public class ConfigurableContractResolver : DefaultContractResolver
{
    // This contract resolver taken from the answer to
    // https://stackoverflow.com/questions/46047308/how-to-add-metadata-to-describe-which-properties-are-dates-in-json-net
    // https://stackoverflow.com/a/46083201/3744182

    readonly object contractCreatedPadlock = new object();
    event EventHandler<ContractCreatedEventArgs> contractCreated;
    int contractCount = 0;

    void OnContractCreated(JsonContract contract, Type objectType)
    {
        EventHandler<ContractCreatedEventArgs> created;
        lock (contractCreatedPadlock)
        {
            contractCount++;
            created = contractCreated;
        }
        if (created != null)
        {
            created(this, new ContractCreatedEventArgs(contract, objectType));
        }
    }

    public event EventHandler<ContractCreatedEventArgs> ContractCreated
    {
        add
        {
            lock (contractCreatedPadlock)
            {
                if (contractCount > 0)
                {
                    throw new InvalidOperationException("ContractCreated events cannot be added after the first contract is generated.");
                }
                contractCreated += value;
            }
        }
        remove
        {
            lock (contractCreatedPadlock)
            {
                if (contractCount > 0)
                {
                    throw new InvalidOperationException("ContractCreated events cannot be removed after the first contract is generated.");
                }
                contractCreated -= value;
            }
        }
    }

    protected override JsonContract CreateContract(Type objectType)
    {
        var contract = base.CreateContract(objectType);
        OnContractCreated(contract, objectType);
        return contract;
    }
}

public class ContractCreatedEventArgs : EventArgs
{
    public JsonContract Contract { get; private set; }
    public Type ObjectType { get; private set; }

    public ContractCreatedEventArgs(JsonContract contract, Type objectType)
    {
        this.Contract = contract;
        this.ObjectType = objectType;
    }
}

public static class ConfigurableContractResolverExtensions
{
    public static ConfigurableContractResolver Configure(this ConfigurableContractResolver resolver, EventHandler<ContractCreatedEventArgs> handler)
    {
        if (resolver == null || handler == null)
            throw new ArgumentNullException();
        resolver.ContractCreated += handler;
        return resolver;
    }
}

然后,创建一种为JsonObjectContract配置Person的方法,如下所示:

public static class JsonContractExtensions
{
    public static void ConfigurePerson(this JsonContract contract)
    {
        if (!typeof(Person).IsAssignableFrom(contract.UnderlyingType))
            return;
        var objectContract = contract as JsonObjectContract;
        if (objectContract == null)
            return;
        var property = objectContract.Properties.Where(p => p.UnderlyingName == nameof(Person.LastName)).Single();
        property.Converter = new AllCapsConverter();
    }
}

最后序列化如下:

// Cache the contract resolver statically for best performance.
var resolver = new ConfigurableContractResolver()
    .Configure((s, e) => { e.Contract.ConfigurePerson(); });

var settigs = new JsonSerializerSettings
{
    ContractResolver = resolver,
};

var person = new Person
{
    FirstName = "George",
    LastName = "Washington"
};
var serialized = JsonConvert.SerializeObject(person, settigs);

注意:

  • 除了创建ConfigurableContractResolver之外,还可以继承DefaultContractResolver的子类,覆盖How to add metadata to describe which properties are dates in JSON.Net并在那里为Person.LastName硬编码必要的逻辑。但是,创建一个可配置的解析器以允许在运行时将自定义进行合并似乎更有用和可重用。

  • AllCapsConverter.WriteJson()中,使用DefaultContractResolver.CreateProperty来写大写字符串会更简单:

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var upper = ((string)value).ToUpperInvariant();
        writer.WriteValue(upper);
    }
    
  • 您可能想writer.WriteValue(string)以获得最佳性能。

提琴cache the contract resolver

答案 1 :(得分:1)

您可以通过编程方式将JsonConverter应用于模型类中的一个或多个属性,而无需通过自定义ContractResolver使用属性。这是一个简单的示例,它将您的AllCapsConverter应用于您的LastName类中的Person属性。 (如果您正在寻找更强大的解决方案,请查看@dbc的answer。我的目的是展示可能可行的最简单示例。)

public class CustomResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty prop = base.CreateProperty(member, memberSerialization);
        if (prop.DeclaringType == typeof(Person) && prop.UnderlyingName == "LastName")
        {
            prop.Converter = new AllCapsConverter();
        }
        return prop;
    }
}

这是更新的测试和Person模型,显示了如何使用解析器:

public class PersonSerializationTest
{
    [Fact]
    public void SerializePerson_LastNameCaps()
    {
        var person = new Person
        {
            FirstName = "George",
            LastName = "Washington"
        };
        var settings = new JsonSerializerSettings
        {
            ContractResolver = new CustomResolver()
        };
        var serialized = JsonConvert.SerializeObject(person, settings);
        var expected = @"{""FirstName"":""George"",""LastName"":""WASHINGTON""}";
        Assert.Equal(expected, serialized);
    }
}

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

正在运行的演示:https://dotnetfiddle.net/o4e3WP