Newtonsoft JSON动态属性名称

时间:2016-06-20 07:52:26

标签: c# json json.net asp.net-web-api2

有没有办法在序列化期间更改Data属性的名称,所以我可以在WEB Api中重用这个类。

例如,如果我要返回分页用户列表,则应将数据属性序列化为"用户",如果我要返回项目列表,则应调用" items& #34;等等。

这样的事情是可能的:

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; }
}

修改

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

实施例

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

6 个答案:

答案 0 :(得分:16)

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

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

public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
{
}

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

然后是解析器:

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";
    }
}

现在,您可以使用PagedData<T>属性修饰[JsonPropertyNameBasedOnItemClass]类中具有可变名称的属性:

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

使用[JsonPluralName]属性装饰您的DTO课程:

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

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

最后,要序列化,创建JsonSerializerSettings的实例,设置ContractResolver属性,并将设置传递给JsonConvert.SerializeObject,如下所示:

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

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

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

如果你正在使用Web API(看起来像你),那么你可以通过Register类的WebApiConfig方法将自定义解析器安装到管道中(在{{1}中)文件夹)。

App_Start

另一种方法

另一种可能的方法是使用自定义JsonSerializerSettings settings = config.Formatters.JsonFormatter.SerializerSettings; settings.ContractResolver = new CustomResolver(); 来处理JsonConverter类的序列化,而不是使用上面提到的更通用的“解析器+属性”方法。转换器方法要求PagedData类上有另一个属性,它指定用于可枚举PagedData属性的JSON名称。您可以在Data构造函数中传递此名称,也可以单独设置,只要在序列化时间之前执行此操作即可。转换器将查找该名称,并在为可枚举属性写出JSON时使用它。

以下是转换器的代码:

PagedData

要使用此转换器,首先将一个名为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(); } } 的字符串属性添加到DataPropertyName类(如果您愿意,可以将其设为私有),然后将PagedData属性添加到该类中把它绑在转换器上:

[JsonConverter]

就是这样。只要您设置了[JsonConverter(typeof(PagedDataConverter))] public class PagedData<T> { private string DataPropertyName { get; set; } public IEnumerable<T> Data { get; private set; } ... } 属性,转换器就会在序列化时将其选中。

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

答案 1 :(得分:6)

另一个选项,不需要使用json格式化程序或使用字符串替换 - 只有继承和覆盖(仍然不是很好的解决方案,imo):

public class MyUser { }
public class MyItem { }

// you cannot use it out of the box, because it's abstract,
// i.e. only for what's intended [=implemented].
public abstract class PaginatedData<T>
{
    // abstract, so you don't forget to override it in ancestors
    public abstract IEnumerable<T> Data { get; }
    public int Count { get; }
    public int CurrentPage { get; }
    public int Offset { get; }
    public int RowsPerPage { get; }
    public int? PreviousPage { get; }
    public int? NextPage { get; }
}

// you specify class explicitly
// name is clear,.. still not clearer than PaginatedData<MyUser> though
public sealed class PaginatedUsers : PaginatedData<MyUser>
{
    // explicit mapping - more agile than implicit name convension
    [JsonProperty("Users")]
    public override IEnumerable<MyUser> Data { get; }
}

public sealed class PaginatedItems : PaginatedData<MyItem>
{
    [JsonProperty("Items")]
    public override IEnumerable<MyItem> Data { get; }
}

答案 2 :(得分:2)

这是一个解决方案,不需要对使用Json序列化程序的方式进行任何更改。实际上,它也应该与其他序列化器一起使用。它使用酷DynamicObject类。

用法就像你想要的那样:

var usersPagedData = new PagedData<User>("Users");
....

public class PagedData<T> : DynamicObject
{
    private string _name;

    public PagedData(string name)
    {
        if (name == null)
            throw new ArgumentNullException(nameof(name));

        _name = name;
    }

    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; }

    public override IEnumerable<string> GetDynamicMemberNames()
    {
        yield return _name;
        foreach (var prop in GetType().GetProperties().Where(p => p.CanRead && p.GetIndexParameters().Length == 0 && p.Name != nameof(Data)))
        {
            yield return prop.Name;
        }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (binder.Name == _name)
        {
            result = Data;
            return true;
        }

        return base.TryGetMember(binder, out result);
    }
}

答案 3 :(得分:1)

我开发了一个名为 SerializationInterceptor 的包。这是 GitHub 链接:https://github.com/Dorin-Mocan/SerializationInterceptor/wiki。您还可以使用 Nuget 包管理器安装该包。

下面的示例使用 Newtonsoft.Json 进行序列化。您可以使用任何其他工具,因为此包不依赖任何工具。

你可以创建一个拦截器

public class JsonPropertyInterceptorAttribute : SerializationInterceptor.Attributes.InterceptorAttribute
{
    public JsonPropertyInterceptorAttribute(string interceptorId)
        : base(interceptorId, typeof(Newtonsoft.Json.JsonPropertyAttribute))
    {
    }

    protected override SerializationInterceptor.Attributes.AttributeBuilderParams Intercept(SerializationInterceptor.Attributes.AttributeBuilderParams originalAttributeBuilderParams, object context)
    {
        string theNameYouWant;
        switch (InterceptorId)
        {
            case "some id":
                theNameYouWant = (string)context;
                break;
            default:
                return originalAttributeBuilderParams;
        }
        originalAttributeBuilderParams.ConstructorArgs = new[] { theNameYouWant };
        return originalAttributeBuilderParams;
    }
}

并将拦截器放在 Data 道具上

public class PagedData<T>
{
    [JsonPropertyInterceptor("some id")]
    [Newtonsoft.Json.JsonProperty("during serialization this value will be replaced with the one passed in context")]
    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; }
}

然后你可以像这样序列化对象

var serializedObj = SerializationInterceptor.Interceptor.InterceptSerialization(
    obj,
    objType,
    (o, t) =>
    {
        var serializer = new Newtonsoft.Json.JsonSerializer();
        using var stream = new MemoryStream();
        using var streamWriter = new StreamWriter(stream);
        using var jsonTextWriter = new Newtonsoft.Json.JsonTextWriter(streamWriter);
        serializer.Serialize(jsonTextWriter, o, t);
        jsonTextWriter.Flush();
        return Encoding.Default.GetString(stream.ToArray());
    },
    context: "the name you want");

希望这对你有用。

答案 4 :(得分:0)

看看这里: this comment

在序列化期间没有完成,但是使用字符串操作。

不太好(在我看来),但至少有可能。

干杯托马斯

答案 5 :(得分:0)

以下是在.NET Standard 2中测试的另一种解决方案。

public class PagedResult<T> where T : class
{

    [JsonPropertyNameBasedOnItemClassAttribute]
    public List<T> Results { get; set; }

    [JsonProperty("count")]
    public long Count { get; set; }

    [JsonProperty("total_count")]
    public long TotalCount { get; set; }

    [JsonProperty("current_page")]
    public long CurrentPage { get; set; }

    [JsonProperty("per_page")]
    public long PerPage { get; set; }

    [JsonProperty("pages")]
    public long Pages { get; set; }
}

我使用Humanizer进行复数化。

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        if (member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
        {
            Type[] arguments = property.DeclaringType.GenericTypeArguments;
            if(arguments.Length > 0)
            {
                string name = arguments[0].Name.ToString();
                property.PropertyName = name.ToLower().Pluralize();
            }
            return property;
        }
        return base.CreateProperty(member, memberSerialization);
    }