Newtonsoft JSON 动态属性名称 [英] Newtonsoft JSON dynamic property name

查看:46
本文介绍了Newtonsoft JSON 动态属性名称的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有没有办法在序列化过程中更改 Data 属性的名称,以便我可以在我的 WEB Api 中重用这个类.

例如,如果我返回分页用户列表,Data 属性应序列化为users",如果我返回项目列表,则应称为items"等.

这样的事情可能吗:

公共类 PagedData{[JsonProperty(PropertyName = "从构造函数设置")]??公共 IEnumerable数据{得到;私人订制;}公共 int 计数 { 得到;私人订制;}公共 int CurrentPage { 获取;私人订制;}公共 int 偏移 { 获取;私人订制;}公共 int RowsPerPage { 获取;私人订制;}公众号?上一页{ 得到;私人订制;}公众号?下一页{ 得到;私人订制;}}

我想控制此功能,例如尽可能传递要使用的名称.如果我的 class 被称为 UserDTO,我仍然希望序列化的属性被称为 Users,而不是 UserDTOs.>

示例

var usersPagedData = new PagedData("Users", params...);

解决方案

您可以使用自定义 ContractResolver 执行此操作.解析器可以查找自定义属性,该属性将表示您希望 JSON 属性的名称基于可枚举项的类.如果项目类上有另一个属性指定了其复数名称,则该名称将用于可枚举属性,否则项目类名称本身将被复数并用作可枚举属性名称.以下是您需要的代码.

首先让我们定义一些自定义属性:

公共类 JsonPropertyNameBasedOnItemClassAttribute : 属性{}公共类 JsonPluralNameAttribute : 属性{公共字符串 PluralName { get;放;}公共 JsonPluralNameAttribute(字符串复数名称){复数名称 = 复数名称;}}

然后是解析器:

公共类 CustomResolver : DefaultContractResolver{受保护的覆盖 JsonProperty CreateProperty(MemberInfo 成员,MemberSerialization memberSerialization){JsonProperty prop = base.CreateProperty(member, memberSerialization);if (prop.PropertyType.IsGenericType && member.GetCustomAttribute() != null){输入 itemType = prop.PropertyType.GetGenericArguments().First();JsonPluralNameAttribute att = itemType.GetCustomAttribute();prop.PropertyName = att != null ?att.PluralName : Pluralize(itemType.Name);}返回道具;}受保护的字符串复数(字符串名称){if (name.EndsWith("y") && !name.EndsWith("ay") && !name.EndsWith("ey") && !name.EndsWith("y")oy") && !name.EndsWith(uy"))return name.Substring(0, name.Length - 1) + "ies";if (name.EndsWith("s"))返回名称+es";返回名称+s";}}

现在您可以使用 [JsonPropertyNameBasedOnItemClass] 属性修饰 PagedData 类中的可变命名属性:

公共类 PagedData{[JsonPropertyNameBasedOnItemClass]公共 IEnumerable数据{得到;私人订制;}...}

并使用 [JsonPluralName] 属性装饰您的 DTO 类:

[JsonPluralName(用户")]公共类 UserDTO{...}[JsonPluralName(项目")]公共类 ItemDTO{...}

最后,要序列化,创建一个 JsonSerializerSettings 的实例,设置 ContractResolver 属性,然后像这样将设置传递给 JsonConvert.SerializeObject:

JsonSerializerSettings settings = new JsonSerializerSettings{ContractResolver = new CustomResolver()};string json = JsonConvert.SerializeObject(pagedData, settings);

小提琴:https://dotnetfiddle.net/GqKBnx

如果您使用的是 Web API(看起来像您一样),那么您可以通过 WebApiConfig 类的 Register 方法将自定义解析器安装到管道中(在 App_Start 文件夹中).

JsonSerializerSettings settings = config.Formatters.JsonFormatter.SerializerSettings;settings.ContractResolver = new CustomResolver();


另一种方法

另一种可能的方法使用自定义 JsonConverter 来专门处理 PagedData 类的序列化,而不是使用更通用的解析器 + 属性";上面介绍的方法.转换器方法要求您的 PagedData 类上有另一个属性,该属性指定用于可枚举 Data 属性的 JSON 名称.您可以在 PagedData 构造函数中传递这个名称,也可以单独设置它,只要您在序列化时间之前这样做.转换器将查找该名称并在为可枚举属性写出 JSON 时使用它.

这是转换器的代码:

公共类 PagedDataConverter : JsonConverter{public override bool CanConvert(Type objectType){返回 objectType.IsGenericType &&objectType.GetGenericTypeDefinition() == typeof(PagedData<>);}public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer){Type type = value.GetType();var bindingFlags = BindingFlags.Instance |BindingFlags.NonPublic |BindingFlags.Public;string dataPropertyName = (string)type.GetProperty(DataPropertyName", bindingFlags).GetValue(value);if (string.IsNullOrEmpty(dataPropertyName)){dataPropertyName = "数据";}JObject jo = new JObject();jo.Add(dataPropertyName, JArray.FromObject(type.GetProperty(Data").GetValue(value)));foreach (PropertyInfo prop in type.GetProperties().Where(p => !p.Name.StartsWith("Data"))){jo.Add(prop.Name, new JValue(prop.GetValue(value)));}jo.WriteTo(作家);}公共覆盖 bool CanRead{得到 { 返回假;}}公共覆盖对象 ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer){抛出新的 NotImplementedException();}}

要使用此转换器,首先将名为 DataPropertyName 的字符串属性添加到您的 PagedData 类(如果您愿意,它可以是私有的),然后添加一个 [JsonConverter] 属性给类以将其绑定到转换器:

[JsonConverter(typeof(PagedDataConverter))]公共类 PagedData{私有字符串 DataPropertyName { 获取;放;}公共 IEnumerable数据{得到;私人订制;}...}

就是这样.只要您设置了 DataPropertyName 属性,转换器就会在序列化时获取它.

小提琴:https://dotnetfiddle.net/8E8fEE

Is there a way to change name of Data property during serialization, so I can reuse this class in my WEB Api.

For an example, if i am returning paged list of users, Data property should be serialized as "users", if i'm returning list of items, should be called "items", etc.

Is something like this possible:

public class PagedData
{
    [JsonProperty(PropertyName = "Set from constructor")]??
    public IEnumerable<T> Data { get; private set; }
    public int Count { get; private set; }
    public int CurrentPage { get; private set; }
    public int Offset { get; private set; }
    public int RowsPerPage { get; private set; }
    public int? PreviousPage { get; private set; }
    public int? NextPage { get; private set; }
}

EDIT:

I would like to have a control over this functionality, such as passing name to be used if possible. If my class is called UserDTO, I still want serialized property to be called Users, not UserDTOs.

Example

var usersPagedData = new PagedData("Users", params...);

解决方案

You can do this with a custom ContractResolver. The resolver can look for a custom attribute which will signal that you want the name of the JSON property to be based on the class of the items in the enumerable. If the item class has another attribute on it specifying its plural name, that name will then be used for the enumerable property, otherwise the item class name itself will be pluralized and used as the enumerable property name. Below is the code you would need.

First let's define some custom attributes:

public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
{
}

public class JsonPluralNameAttribute : Attribute
{
    public string PluralName { get; set; }
    public JsonPluralNameAttribute(string pluralName)
    {
        PluralName = pluralName;
    }
}

And then the resolver:

public class CustomResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty prop = base.CreateProperty(member, memberSerialization);
        if (prop.PropertyType.IsGenericType && member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
        {
            Type itemType = prop.PropertyType.GetGenericArguments().First();
            JsonPluralNameAttribute att = itemType.GetCustomAttribute<JsonPluralNameAttribute>();
            prop.PropertyName = att != null ? att.PluralName : Pluralize(itemType.Name);
        }
        return prop;
    }

    protected string Pluralize(string name)
    {
        if (name.EndsWith("y") && !name.EndsWith("ay") && !name.EndsWith("ey") && !name.EndsWith("oy") && !name.EndsWith("uy"))
            return name.Substring(0, name.Length - 1) + "ies";

        if (name.EndsWith("s"))
            return name + "es";

        return name + "s";
    }
}

Now you can decorate the variably-named property in your PagedData<T> class with the [JsonPropertyNameBasedOnItemClass] attribute:

public class PagedData<T>
{
    [JsonPropertyNameBasedOnItemClass]
    public IEnumerable<T> Data { get; private set; }
    ...
}

And decorate your DTO classes with the [JsonPluralName] attribute:

[JsonPluralName("Users")]
public class UserDTO
{
    ...
}

[JsonPluralName("Items")]
public class ItemDTO
{
    ...
}

Finally, to serialize, create an instance of JsonSerializerSettings, set the ContractResolver property, and pass the settings to JsonConvert.SerializeObject like so:

JsonSerializerSettings settings = new JsonSerializerSettings
{
    ContractResolver = new CustomResolver()
};

string json = JsonConvert.SerializeObject(pagedData, settings);

Fiddle: https://dotnetfiddle.net/GqKBnx

If you're using Web API (looks like you are), then you can install the custom resolver into the pipeline via the Register method of the WebApiConfig class (in the App_Start folder).

JsonSerializerSettings settings = config.Formatters.JsonFormatter.SerializerSettings;
settings.ContractResolver = new CustomResolver();


Another Approach

Another possible approach uses a custom JsonConverter to handle the serialization of the PagedData class specifically instead using the more general "resolver + attributes" approach presented above. The converter approach requires that there be another property on your PagedData class which specifies the JSON name to use for the enumerable Data property. You could either pass this name in the PagedData constructor or set it separately, as long as you do it before serialization time. The converter will look for that name and use it when writing out JSON for the enumerable property.

Here is the code for the converter:

public class PagedDataConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(PagedData<>);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Type type = value.GetType();

        var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
        string dataPropertyName = (string)type.GetProperty("DataPropertyName", bindingFlags).GetValue(value);
        if (string.IsNullOrEmpty(dataPropertyName)) 
        {
            dataPropertyName = "Data";
        }

        JObject jo = new JObject();
        jo.Add(dataPropertyName, JArray.FromObject(type.GetProperty("Data").GetValue(value)));
        foreach (PropertyInfo prop in type.GetProperties().Where(p => !p.Name.StartsWith("Data")))
        {
            jo.Add(prop.Name, new JValue(prop.GetValue(value)));
        }
        jo.WriteTo(writer);
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

To use this converter, first add a string property called DataPropertyName to your PagedData class (it can be private if you like), then add a [JsonConverter] attribute to the class to tie it to the converter:

[JsonConverter(typeof(PagedDataConverter))]
public class PagedData<T>
{
    private string DataPropertyName { get; set; }
    public IEnumerable<T> Data { get; private set; }
    ...
}

And that's it. As long as you've set the DataPropertyName property, it will be picked up by the converter on serialization.

Fiddle: https://dotnetfiddle.net/8E8fEE

这篇关于Newtonsoft JSON 动态属性名称的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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