[英]Newtonsoft JSON dynamic property name
有沒有辦法在序列化過程中更改 Data 屬性的名稱,以便我可以在我的 WEB Api 中重用這個類。
例如,如果我要返回分頁用戶列表,則數據屬性應序列化為“用戶”,如果我要返回項目列表,則應稱為“項目”等。
這樣的事情可能嗎:
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 屬性的名稱基於可枚舉項的類。 如果項目類上有另一個屬性指定了其復數名稱,則該名稱將用於可枚舉屬性,否則項目類名稱本身將被復數並用作可枚舉屬性名稱。 下面是您需要的代碼。
首先讓我們定義一些自定義屬性:
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
類(在App_Start
文件夾中)的Register
方法將自定義解析器安裝到管道中。
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]
屬性以將其綁定到轉換器:
[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-only 屬性,但我很確定它確實在 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);
}
}
我開發了一個名為 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");
希望這對你有用。
以下是在 .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);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.