web-dev-qa-db-fra.com

Nom de la propriété dynamique Newtonsoft JSON

Existe-t-il un moyen de changer le nom de la propriété Data lors de la sérialisation afin de pouvoir réutiliser cette classe dans mon API Web?.

Par exemple, si je renvoie une liste d'utilisateurs paginée, la propriété de données doit être sérialisée en tant qu'utilisateur, si elle renvoie une liste d'éléments, elle doit être appelée "éléments", etc.

Est-ce que quelque chose comme ceci est possible:

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

MODIFIER:

Je voudrais avoir un contrôle sur cette fonctionnalité, telle que transmettre le nom à utiliser si possible. Si ma class s'appelle UserDTO, je souhaite tout de même que la propriété sérialisée s'appelle Users et non UserDTOs.

Exemple

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

Vous pouvez le faire avec une ContractResolver personnalisée. Le résolveur peut rechercher un attribut personnalisé indiquant que vous souhaitez que le nom de la propriété JSON soit basé sur la classe des éléments de l'énumérable. Si la classe d'élément comporte un autre attribut spécifiant son nom au pluriel, ce nom sera alors utilisé pour la propriété énumérable, sinon le nom de la classe d'élément lui-même sera pluralisé et utilisé comme nom de propriété énumérable. Vous trouverez ci-dessous le code dont vous auriez besoin.

Commençons par définir des attributs personnalisés:

public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
{
}

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

Et puis le résolveur:

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

Vous pouvez maintenant décorer la propriété de nom variable dans votre classe PagedData<T> avec l'attribut [JsonPropertyNameBasedOnItemClass]:

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

Et décorez vos classes DTO avec l'attribut [JsonPluralName]:

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

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

Enfin, pour sérialiser, créez une instance de JsonSerializerSettings, définissez la propriété ContractResolver et transmettez les paramètres à JsonConvert.SerializeObject comme suit:

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

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

Fiddle: https://dotnetfiddle.net/GqKBnx

Si vous utilisez l'API Web (vous ressemblez), vous pouvez installer le résolveur personnalisé dans le pipeline via la méthode Register de la classe WebApiConfig (dans le dossier App_Start).

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

Une autre approche

Une autre approche possible utilise une variable JsonConverter personnalisée pour gérer la sérialisation de la classe PagedData au lieu d'utiliser l'approche plus générale "résolveur + attributs" présentée ci-dessus. L'approche du convertisseur nécessite qu'il y ait une autre propriété sur votre classe PagedData qui spécifie le nom JSON à utiliser pour la propriété énumérable Data. Vous pouvez soit transmettre ce nom dans le constructeur PagedData, soit le définir séparément, tant que vous le faites avant la sérialisation. Le convertisseur cherchera ce nom et l'utilisera lors de l'écriture de JSON pour la propriété énumérable.

Voici le code pour le convertisseur:

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

Pour utiliser ce convertisseur, commencez par ajouter une propriété de chaîne appelée DataPropertyName à votre classe PagedData (elle peut être privée si vous le souhaitez), puis ajoutez un attribut [JsonConverter] à la classe pour la lier au convertisseur:

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

Et c'est tout. Tant que vous avez défini la propriété DataPropertyName, elle sera capturée par le convertisseur lors de la sérialisation.

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

15
Brian Rogers

Une autre option qui n’a pas besoin de jouer avec les formateurs JSON ni d’utiliser des substituts de chaîne - seulement l’héritage et le dépassement (solution pas très belle, 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

Voici une solution qui ne nécessite aucune modification de la manière dont vous utilisez le sérialiseur Json. En fait, cela devrait également fonctionner avec d'autres sérialiseurs. Il utilise le cool DynamicObject class.

L'utilisation est juste comme tu voulais:

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

Voici une autre solution testée dans .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; }
}

J'utilise Humanizer pour la pluralisation.

    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

regardez ici: Comment renommer une clé JSON

Ce n'est pas fait pendant la sérialisation mais avec une opération de chaîne.

Pas très gentil (à mes yeux) mais au moins une possibilité.

A bientôt Thomas

0
Thomas Voß