wake-up-neo.net

Dynamischer Name der Newtonsoft JSON-Eigenschaft

Gibt es eine Möglichkeit, den Namen der Data-Eigenschaft während der Serialisierung zu ändern, sodass ich diese Klasse in meiner WEB-API verwenden kann.

Wenn ich beispielsweise eine seitenweise Liste von Benutzern zurücksende, sollte die Data-Eigenschaft als "Benutzer" serialisiert werden. Wenn ich eine Liste von Elementen zurücksende, sollte dies als "Elemente" bezeichnet werden.

Ist so etwas möglich:

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

BEARBEITEN:

Ich möchte eine Kontrolle über diese Funktionalität haben, z. B. die Weitergabe des Namens, der nach Möglichkeit verwendet werden soll. Wenn meine classUserDTO heißt, möchte ich trotzdem, dass die serialisierte Eigenschaft Users und nicht UserDTOs heißt.

Beispiel

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

Sie können dies mit einer benutzerdefinierten ContractResolver tun. Der Resolver kann nach einem benutzerdefinierten Attribut suchen, das signalisiert, dass der Name der JSON-Eigenschaft auf der Klasse der Elemente in der Auflistung basieren soll. Wenn für die Elementklasse ein anderes Attribut festgelegt ist, das ihren Pluralnamen angibt, wird dieser Name für die Enumerable-Eigenschaft verwendet. Andernfalls wird der Elementklassenname selbst pluralisiert und als Enumerable-Eigenschaftenname verwendet. Unten finden Sie den Code, den Sie benötigen.

Zunächst definieren wir einige benutzerdefinierte Attribute:

public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
{
}

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

Und dann der 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";
    }
}

Jetzt können Sie die variabel benannte Eigenschaft in Ihrer PagedData<T>-Klasse mit dem [JsonPropertyNameBasedOnItemClass]-Attribut dekorieren:

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

Und dekorieren Sie Ihre DTO-Klassen mit dem Attribut [JsonPluralName]:

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

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

Erstellen Sie zum Serialisieren schließlich eine Instanz von JsonSerializerSettings, legen Sie die Eigenschaft ContractResolver fest, und übergeben Sie die Einstellungen wie folgt an JsonConvert.SerializeObject:

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

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

Geige: https://dotnetfiddle.net/GqKBnx

Wenn Sie die Web-API verwenden (Sieht so aus, wie Sie sind), können Sie den benutzerdefinierten Resolver mithilfe der Register-Methode der WebApiConfig-Klasse (im Ordner App_Start) in der Pipeline installieren.

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

Ein anderer Ansatz

Ein anderer möglicher Ansatz verwendet eine benutzerdefinierte JsonConverter, um die Serialisierung der PagedData-Klasse spezifisch zu handhaben, stattdessen den oben dargestellten allgemeineren "Auflöser + Attribute" -Ansatz zu verwenden. Der Konverteransatz erfordert, dass eine andere Eigenschaft in Ihrer PagedData-Klasse vorhanden ist, die den JSON-Namen angibt, der für die enumerable Data-Eigenschaft verwendet werden soll. Sie können diesen Namen entweder im Konstruktor PagedData übergeben oder separat festlegen, sofern Sie dies vor der Serialisierungszeit tun. Der Konverter sucht nach diesem Namen und verwendet ihn, wenn JSON für die Enumerable-Eigenschaft ausgegeben wird.

Hier ist der Code für den Konverter:

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

Um diesen Konverter zu verwenden, fügen Sie zunächst eine String-Eigenschaft mit dem Namen DataPropertyName zu Ihrer PagedData-Klasse hinzu (diese kann auf Wunsch auch privat sein), und fügen Sie dann der Klasse ein [JsonConverter]-Attribut hinzu, um es mit dem Konverter zu verknüpfen:

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

Und das ist es. Solange Sie die DataPropertyName -Eigenschaft festgelegt haben, wird sie bei der Serialisierung vom Konverter abgerufen.

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

15
Brian Rogers

Eine weitere Option, ohne mit json-Formatierern spielen zu müssen oder String-Ersetzungen zu verwenden - nur Vererbung und Überschreibung (immer noch keine sehr schöne Lösung, 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; }
}
6
pkuderov

Hier ist eine Lösung, die keine Änderung der Verwendung des Json-Serializers erfordert. In der Tat sollte es auch mit anderen Serialisierern funktionieren. Es verwendet die cool DynamicObject Klasse.

Die Verwendung ist genau so, wie Sie es wollten:

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);
    }
}
2
Simon Mourier

Das Folgende ist eine andere Lösung, die in .NET Standard 2 getestet wurde.

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

Ich verwende Humanizer für die Pluralisierung.

    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);
    }
0
gv1507

schauen Sie hier: So benennen Sie den JSON-Schlüssel um

Dies wird nicht während der Serialisierung durchgeführt, sondern mit einer Stringoperation.

Nicht sehr nett (in meinen Augen), aber zumindest eine Möglichkeit.

Prost, Thomas

0
Thomas Voß