在自定义 json 转换器中访问 .NET 类的自定义属性

dks*_*dks 4 json.net asp.net-web-api

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

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

public class Candidate
{
    public string CandidateName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这是我的自定义 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

public void Post(ComplexType complexTypeParameter){
}
Run Code Online (Sandbox Code Playgroud)

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

 public class Candidate
 {
     [StringSanitizingOptions(Option.ToLowerCase)]
     public string CandidateName { get; set; }
 }
Run Code Online (Sandbox Code Playgroud)

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

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

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

  2. 试图查看我是否可以提取ReadJson()方法内部属性的父类,以便我可以对类应用反射以提取赋予其任何属性的自定义属性,但我也无法提取。

dbc*_*dbc 5

包含对象的堆栈不可用于JsonConverter.ReadJson(),因此您无法在 内部做您想做的事情ReadJson()

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

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来,ConfigurableContractResolverHow to add metadata 中抓取描述 JSON.Net 中哪些属性是日期,并定义扩展方法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
    // /sf/ask/3223311591/
    // /sf/answers/3225824101/

    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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,最后你可以反序列化和序列化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);
Run Code Online (Sandbox Code Playgroud)

笔记:

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

    • 简单。
    • 一个JSON对象是“一个无序的名称/值对的”,所以试图访问包含.NET对象,而读一个属性值不保证工作,因为所需的信息可能没有在尚未阅读(和甚至可能还没有构造父级)。
  2. 由于 的默认实例StringSanitizingConverter应用于为string自己生成的合约,因此无需将转换器添加到JsonSerializer.SettingsConverters。这反过来可能会导致小的性能增强,因为CanConvert将不再被调用。

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

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

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

示例工作 .Net 小提琴在这里


归档时间:

查看次数:

532 次

最近记录:

7 年,8 月 前