牛顿软件 JSON 动态属性名称



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

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

像这样的事情可能吗:

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...);

您可以使用自定义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";
}
}

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

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(看起来像是),则可以通过WebApiConfig类的Register方法(在App_Start文件夹中)将自定义解析程序安装到管道中。

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

另一种方法

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

这是转换器的代码:

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

仅此而已。 只要设置了DataPropertyName属性,转换器就会在序列化时选取该属性。

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

UPD 2020 年 9 月:@RyanHarlich指出,提出的解决方案不是开箱即用的。我发现Newtonsoft.Json在较新版本中没有初始化仅getter属性,但我很确定它确实在ATM我在2016年写了这个答案(没有证据,对不起:)。

一个快速肮脏的解决方案是将公共资源库添加到所有属性(例如在dotnetfiddle中)。我鼓励您找到一个更好的解决方案,为数据对象保留只读界面。我已经 3 年没有使用 .Net 了,所以自己无法为您提供该解决方案,抱歉:/


另一个选项,无需使用 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; }
}

这是一个不需要对使用 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);
}
}

下面是在 .NET 标准 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);
}

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

下面的示例使用Syste.Text.Json进行序列化。您可以使用任何其他序列化程序(Newtonsoft.Json除外)。有关为什么不允许Newtonsoft.Json的更多信息,请参阅 GitHub 文档。

您可以创建拦截器

public class JsonPropertyNameInterceptorAttribute : InterceptorAttribute
{
public JsonPropertyNameInterceptorAttribute(string interceptorId)
: base(interceptorId, typeof(JsonPropertyNameAttribute))
{
}
protected override void Intercept(in AttributeParams originalAttributeParams, object context)
{
string theNameYouWant;
switch (InterceptorId)
{
case "some id":
theNameYouWant = (string)context;
break;
default:
return;
}
originalAttributeParams.ConstructorArgs.First().ArgValue = theNameYouWant;
}
}

并将拦截器放在数据道具上

public class PagedData<T>
{
[JsonPropertyNameInterceptor("some id")]
[JsonPropertyName("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 = InterceptSerialization(
obj,
objType,
(o, t) =>
{
return JsonSerializer.Serialize(o, t, new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve });
},
context: "the name you want");

希望这对你有用。

看看这里: 如何重命名 JSON 密钥

它不是在序列化期间完成的,而是通过字符串操作完成的。

不是很好(在我看来),但至少是一种可能性。

干杯托马斯

相关内容

  • 没有找到相关文章

最新更新