问题描述
在我的项目中,我编写了一个自定义 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,
不存在于
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.
推荐答案
包含对象的堆栈不适用于 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);
}
}
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
本身生成的合约,因此无需将转换器添加到 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.net-web-api'" rel="tag">asp.net-web-api,参见 JsonSerializerSettings 和 Asp.Net Core,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 fiddle 工作示例这里.
Sample working .Net fiddle here.
这篇关于在自定义 json 转换器中访问 .NET 类的自定义属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!