簡體   English   中英

Newtonsoft JSON 動態屬性名稱

[英]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);
    }

看看這里: 如何重命名 JSON 密鑰

它不是在序列化過程中完成的,而是通過字符串操作完成的。

不是很好(在我看來),但至少有可能。

干杯托馬斯

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM