在我的项目中,我编写了一个自定义json转换器来修剪字符串属性中存在的空格。
以下是我们将使用的典型课程的示例
public class Candidate
{
public string CandidateName { get; set; }
}
这是我的自定义json转换器
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());
}
}
使用我的自定义转换器,我现在可以通过使用我的候选'修剪发送到操作方法的任何空格来格式化字符串。作为其参数之一。
public void Post(ComplexType complexTypeParameter){
}
到目前为止一切运作良好。我后来想要增强这个json转换器,根据在Candidate类中设置为string属性的属性来格式化字符串属性。例如,假设我已经编写了这样的候选类,
public class Candidate
{
[StringSanitizingOptions(Option.ToLowerCase)]
public string CandidateName { get; set; }
}
如果我想根据json转换器中的自定义属性配置格式化类的字符串属性,我无法在自定义转换器的ReadJson方法中访问此自定义属性及其配置。
这是我到目前为止所尝试的但没有运气,
不在CustomAttributes
的{{1}}属性中
参数发送到objectType
方法。
试图查看是否可以在ReadJson()
方法中提取属性的父类,以便我可以在类上应用反射来提取赋予其任何属性的自定义属性,但是我也无法提取它。
答案 0 :(得分:0)
JsonConverter.ReadJson()
无法使用包含对象的堆栈,因此您无法在ReadJson()
内执行所需操作。
相反,您可以做的是创建一个custom contract resolver,根据正在为其生成合同的对象的属性,应用适当配置的StringSanitizingConverter
实例。
首先,让我们说您的数据模型,属性和JsonConverter
如下所示(我必须修改一些内容以使您的代码编译并包含一些其他测试用例):< / p>
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);
}
}
接下来,从How to add metadata to describe which properties are dates in JSON.Net抓取ConfigurableContractResolver
,并定义扩展方法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
:
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()
中没有包含对象的堆栈。可能性包括:
由于StringSanitizingConverter
的默认实例适用于为string
生成的合同,因此无需将转换器添加到JsonSerializer.SettingsConverters
。这反过来可能会导致性能提升,因为CanConvert
将不再被调用。
JsonProperty.MemberConverter
最近在Json.NET 11.0.1中被标记为过时,但必须在以前版本的Json.NET中设置为与JsonProperty.Converter
相同的值。如果您使用的是11.0.1或更新版本,则应该可以删除该设置。
您可能需要cache the contract resolver才能获得最佳效果。
要修改asp.net-web-api中的JsonSerializerSettings
,请参阅 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? 或 ASP.NET Core API JSON serializersettings per request ,具体取决于您的要求和正在使用的框架版本。
示例工作.Net小提琴here。