在自定义json转换器中访问.NET类的自定义属性 [英] Access custom attributes of .NET class inside custom json converter
问题描述
在我的项目中,我编写了一个自定义json转换器,以修剪string属性中存在的空白.
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类中设置为string属性的属性来格式化字符串属性.例如,假设我已经编写了这样的候选类,
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,
-
在
objectType
的CustomAttributes
属性中不存在 参数发送到ReadJson()
方法.
Not present in the
CustomAttributes
property of theobjectType
parameter sent to theReadJson()
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.
推荐答案
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
,以及定义扩展方法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);
注意:
-
我不知道为什么
ReadJson()
中没有包含对象的堆栈.可能性包括:
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
本身生成的合同,因此不必将转换器添加到 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核心 , Web 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小提琴示例工作此处.
Sample working .Net fiddle here.
这篇关于在自定义json转换器中访问.NET类的自定义属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!