在自定义 json 转换器中访问 .NET 类的自定义属性 [英] Access custom attributes of .NET class inside custom json converter

查看:21
本文介绍了在自定义 json 转换器中访问 .NET 类的自定义属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的项目中,我编写了一个自定义 json 转换器来修剪字符串属性中存在的空格.

In my project, I have written a custom json converter to trim the white-spaces present in the string property.

这是我们将使用的典型类的示例,

Here is an example of the typical class we will use,

public class Candidate
{
    public string CandidateName { get; set; }
}

这是我的自定义 json 转换器

Here is my custom json converter

public class StringSanitizingConverter : JsonConverter
{       
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(string);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue , JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.String)
            if (reader.Value != null)
            {
                string sanitizedString = (reader.Value as string).Trim();

                if (StringSanitizeOptions.HasFlag(StringSanitizeOptions.ToLowerCase))
                    sanitizedString = sanitizedString.ToLowerInvariant();

                return sanitizedString;
            }

        return reader.Value;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var text = (string)value;
        if (text == null)
            writer.WriteNull();
        else
            writer.WriteValue(text.Trim());
    }
}

使用我的自定义转换器,我现在可以使用我的候选人"作为其参数之一,通过修剪发送到操作方法的任何空格来格式化字符串.

With my custom converter I am now able to format the string by trimming any white-spaces present sent to the action methods using my 'Candidate' as one of its parameter.

public void Post(ComplexType complexTypeParameter){
}

到目前为止一切正常.后来我想增强这个 json 转换器,以根据在 Candidate 类中设置为字符串属性的属性来格式化字符串属性.例如,假设我已经这样编写了我的候选类,

Everything worked well so far. I later wanted to enhance this json converter to format the string properties based on the attributes set to the string property in the Candidate class. for example, assume I have written my candidate class like this,

 public class Candidate
 {
     [StringSanitizingOptions(Option.ToLowerCase)]
     public string CandidateName { get; set; }
 }

如果我想根据 json 转换器中的自定义属性配置来格式化类的字符串属性,我无法在自定义转换器的 ReadJson 方法中访问此自定义属性及其配置.

And if I wanted to format the string properties of a class based on the custom attribute configuration inside the json converter , I am not able to access this custom attribute and its configuration inside the ReadJson method of the custom converter.

这是我迄今为止尝试过的,但没有运气,

Here is what I have tried so far but with no luck,

  1. 不存在于 objectTypeCustomAttributes 属性中
    参数发送到 ReadJson() 方法.

  1. Not present in the CustomAttributes property of the objectType
    parameter sent to the ReadJson() method.

试图查看是否可以在 ReadJson() 方法中提取属性的父类,以便我可以对类应用反射以提取给定的自定义属性它的任何属性,但我也无法提取它.

Was trying to see if I could extract the parent class of the property inside the ReadJson() method, so that I could apply reflection on the class to extract the custom attributes given to any of its property,but I could not extract that too.

推荐答案

包含对象的堆栈不适用于 JsonConverter.ReadJson(),因此你无法在 ReadJson() 中做你想做的事.

The stack of containing object(s) is not made available to JsonConverter.ReadJson(), thus you cannot do what you want inside ReadJson().

相反,您可以创建一个自定义合约解析器 根据正在为其生成合同的对象的属性应用适当配置的 StringSanitizingConverter 实例.

Instead, what you can do is to create a custom contract resolver that applies an appropriately configured instance of StringSanitizingConverter based on the properties of the object for which a contract is being generated.

首先,假设您的数据模型、属性和 JsonConverter 如下所示(我必须修改一些内容以使您的代码能够编译并包含一些额外的测试用例):

First, let's say your data model, attribute, and JsonConverter look like the following (where I had to modify a few things to make your code compile and include some additional test cases):

public class Candidate
{
    [StringSanitizingOptions(Option.ToLowerCase)]
    public string CandidateName { get; set; }

    [StringSanitizingOptions(Option.DoNotTrim)]
    public string StringLiteral { get; set; }

    public string DefaultString { get; set; }

    public List<string> DefaultStrings { get; set; }
}

[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class StringSanitizingOptionsAttribute : System.Attribute
{
    public Option StringSanitizeOptions { get; set; }

    public StringSanitizingOptionsAttribute(Option stringSanitizeOptions)
    {
        this.StringSanitizeOptions = stringSanitizeOptions;
    }
}

[Flags]
public enum Option
{
    Default = 0,
    ToLowerCase = (1<<0),
    DoNotTrim = (1<<1),
}

public static class StringSanitizeOptionsExtensions
{
    public static bool HasFlag(this Option options, Option flag)
    {
        return (options & flag) == flag;
    }
}

public class StringSanitizingConverter : JsonConverter
{
    readonly Option options;

    public StringSanitizingConverter() : this(Option.Default) { }

    public StringSanitizingConverter(Option options)
    {
        this.options = options;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(string);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.String)
            if (reader.Value != null)
            {
                var sanitizedString = (reader.Value as string);

                if (!options.HasFlag(Option.DoNotTrim))
                    sanitizedString = sanitizedString.Trim();

                if (options.HasFlag(Option.ToLowerCase))
                    sanitizedString = sanitizedString.ToLowerInvariant();

                return sanitizedString;
            }

        return reader.Value;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // WriteJson is never called with null
        var text = (string)value;

        if (!options.HasFlag(Option.DoNotTrim))
            text = text.Trim();

        writer.WriteValue(text);
    }
}

接下来,从 如何添加元数据来描述 JSON.Net 中哪些属性是日期ConfigurableContractResolver/a>,并定义扩展方法JsonContractExtensions.AddStringConverters():

Next, grab ConfigurableContractResolver from How to add metadata to describe which properties are dates in JSON.Net, and define the extension method JsonContractExtensions.AddStringConverters():

public static class JsonContractExtensions
{
    public static JsonContract AddStringConverters(this JsonContract contract)
    {
        if (contract is JsonPrimitiveContract)
        {
            if (contract.UnderlyingType == typeof(string))
                contract.Converter = new StringSanitizingConverter();
        }
        else if (contract is JsonObjectContract)
        {
            var objectContract = (JsonObjectContract)contract;
            foreach (var property in objectContract.Properties)
            {
                if (property.PropertyType == typeof(string))
                {
                    var attr = property.AttributeProvider.GetAttributes(typeof(StringSanitizingOptionsAttribute), true)
                        .Cast<StringSanitizingOptionsAttribute>()
                        .SingleOrDefault();
                    if (attr != null)
                    {
                        property.Converter = property.MemberConverter = new StringSanitizingConverter(attr.StringSanitizeOptions);
                    }
                }
            }
        }
        return contract;
    }
}

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

那么,最后就可以对Candidate进行反序列化和序列化了,如下:

Then, finally you can deserialize and serialize Candidate as follows:

var settings = new JsonSerializerSettings
{
    ContractResolver = new ConfigurableContractResolver
    {
    }.Configure((s, e) => { e.Contract.AddStringConverters(); }),
};

var candidate = JsonConvert.DeserializeObject<Candidate>(json, settings);

var json2 = JsonConvert.SerializeObject(candidate, Formatting.Indented, settings);

注意事项:

  1. 我不知道为什么包含对象的堆栈在 ReadJson() 中不可用.可能性包括:

  1. I don't know why the stack of containing object(s) is not available in ReadJson(). Possibilities include:

  • 简单.
  • JSON 对象是一组无序的名称/值对",因此尝试访问包含.Net 对象在读取属性值时不能保证正常工作,因为可能尚未读入所需的信息(甚至可能尚未构造父对象).
  • Simplicity.
  • A JSON object is "an unordered set of name/value pairs", so trying to access the containing .Net object while reading a property value isn't guaranteed to work, since the information required might not have been read in yet (and the parent might not even have been constructed).

由于 StringSanitizingConverter 的默认实例应用于为 string 本身生成的合约,因此无需将转换器添加到 JsonSerializer.SettingsConverters.这反过来可能会导致小的性能提升,如 CanConvert 将不再被调用.

Because a default instance of StringSanitizingConverter is applied to the contract generated for string itself, it is not necessary to add the converter to JsonSerializer.SettingsConverters. This in turn may lead to a small performance enhancement as CanConvert will no longer get called.

JsonProperty.MemberConverter 最近在 Json.NET 11.0.1 中被标记为已过时,但是必须设置为与以前版本的 Json.NET 中的 JsonProperty.Converter 相同的值.如果您使用的是 11.0.1 或更新的版本,您应该可以删除该设置.

JsonProperty.MemberConverter was recently marked obsolete in Json.NET 11.0.1 but must be set to the same value as JsonProperty.Converter in previous versions of Json.NET. If you are using 11.0.1 or a more recent version you should be able to remove the setting.

您可能希望缓存合约解析器以获得最佳性能.

You may want to cache the contract resolver for best performance.

修改,参见 JsonSerializerSettings 和 Asp.Net CoreWeb API:在操作或控制器级别配置 JSON 序列化程序设置如何在 MVC 4 Web API 中为 Json.NET 设置自定义 JsonSerializerSettings?ASP.NET Core API JSON 序列化程序设置每个请求,具体取决于您的要求和使用的框架版本.

To modify JsonSerializerSettings in asp.net-web-api, see JsonSerializerSettings and Asp.Net Core, Web API: Configure JSON serializer settings on action or controller level, How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API? or ASP.NET Core API JSON serializersettings per request, depending on your requirements and the version of the framework in use.

.Net fiddle 工作示例这里.

Sample working .Net fiddle here.

这篇关于在自定义 json 转换器中访问 .NET 类的自定义属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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