Ordre des champs sérialisés à l'aide de JSON.NET

138

Existe-t-il un moyen de spécifier l'ordre des champs dans un objet JSON sérialisé à l'aide de JSON.NET ?

Il suffirait de préciser qu'un seul champ apparaît toujours en premier.

Kevin Montrose
la source
7
Je pense qu'il est probablement intéressé à montrer d'abord le champ ID (ou similaire), puis tous les autres champs. c'est plus convivial pour les utilisateurs finaux que de le rechercher après des champs commençant par A..I
Michael Bahig
3
Les propriétés JSON sont définies comme non ordonnées. Je pense que c'est tout à fait correct de forcer un ordre de SORTIE particulier pendant la sérialisation (pour le plaisir de regarder le JSON peut-être), mais ce serait une mauvaise décision de créer une DÉPENDANCE sur un ordre particulier de désérialisation.
DaBlick
5
Quelques raisons valables: (1) simuler une propriété "$ type" qui doit être la première propriété du JSON, (2) essayer de générer un JSON qui compresse autant que possible
Stephen Chung
4
Une autre raison pourrait être (3) une représentation canonique qui utilise la syntaxe JSON - le même objet doit être garanti pour produire la même chaîne JSON. Un ordre déterministe des attributs est une condition préalable nécessaire pour cela.
MarkusSchaber
2
Kevin, pouvez-vous mettre à jour la réponse acceptée à cette question?
Millie Smith

Réponses:

256

La méthode prise en charge consiste à utiliser l' JsonPropertyattribut sur les propriétés de classe pour lesquelles vous souhaitez définir l'ordre. Lisez la documentation de la commande JsonPropertyAttribute pour plus d'informations.

Passez la valeur JsonPropertyan Orderet le sérialiseur s'occupera du reste.

 [JsonProperty(Order = 1)]

Ceci est très similaire au

 DataMember(Order = 1) 

des System.Runtime.Serializationjours.

Voici une note importante de @ kevin-babcock

... définir l'ordre sur 1 ne fonctionnera que si vous définissez un ordre supérieur à 1 sur toutes les autres propriétés. Par défaut, toute propriété sans paramètre Order recevra un ordre de -1. Vous devez donc soit donner toutes les propriétés sérialisées et commander, soit définir votre premier élément sur -2

Steve
la source
97
L'utilisation de la Orderpropriété de JsonPropertyAttributepeut être utilisée pour contrôler l'ordre dans lequel les champs sont sérialisés / désérialisés. Cependant, définir l'ordre sur 1 ne fonctionnera que si vous définissez un ordre supérieur à 1 sur toutes les autres propriétés. Par défaut, toute propriété sans paramètre Order recevra un ordre de -1. Vous devez donc soit donner toutes les propriétés sérialisées et l'ordre, soit définir votre premier élément sur -2.
Kevin Babcock
1
Cela fonctionne pour la sérialisation, mais la commande n'est pas prise en compte lors de la désérialisation. Selon la documentation, l'attribut order est utilisé à la fois pour la sérialisation et la désérialisation. Y at-il un travail autour?
cangosta
1
Existe-t-il une propriété similaire pour le JavaScriptSerializer.
Shimmy Weitzhandler
4
@cangosta L'ordre de désérialisation ne devrait pas avoir d'importance .. sauf dans certains cas d'attente très "étranges".
user2864740
1
Lisez la discussion similaire sur les problèmes de github autour du désir que l'Ordre soit respecté dans la désérialisation: github.com/JamesNK/Newtonsoft.Json/issues/758 Fondamentalement, aucune chance de celui-ci.
Tyeth
126

Vous pouvez en fait contrôler l'ordre en implémentant IContractResolverou en remplaçant la méthode DefaultContractResolvers CreateProperties.

Voici un exemple de ma mise en œuvre simple IContractResolverqui classe les propriétés par ordre alphabétique:

public class OrderedContractResolver : DefaultContractResolver
{
    protected override System.Collections.Generic.IList<JsonProperty> CreateProperties(System.Type type, MemberSerialization memberSerialization)
    {
        return base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName).ToList();
    }
}

Et puis définissez les paramètres et sérialisez l'objet, et les champs JSON seront dans l'ordre alphabétique:

var settings = new JsonSerializerSettings()
{
    ContractResolver = new OrderedContractResolver()
};

var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
Mattias Nordberg
la source
11
C'est très utile (+1) mais une mise en garde: il semble que la sérialisation des dictionnaires n'utilise pas cette personnalisation CreateProperties. Ils sérialisent bien mais ne finissent pas par être triés. Je suppose qu'il existe une manière différente de personnaliser la sérialisation des dictionnaires, mais je ne l'ai pas trouvée.
solublefish
Parfait. Fait exactement ce que je voulais. Merci.
Wade Hatler
C'est une excellente solution. Cela a parfaitement fonctionné pour moi, en particulier lors de la mise côte à côte de 2 objets JSON et de l'alignement des propriétés.
Vince
16

Dans mon cas, la réponse de Mattias n'a pas fonctionné. La CreatePropertiesméthode n'a jamais été appelée.

Après quelques débogages des Newtonsoft.Jsoninternes, j'ai trouvé une autre solution.

public class JsonUtility
{
    public static string NormalizeJsonString(string json)
    {
        // Parse json string into JObject.
        var parsedObject = JObject.Parse(json);

        // Sort properties of JObject.
        var normalizedObject = SortPropertiesAlphabetically(parsedObject);

        // Serialize JObject .
        return JsonConvert.SerializeObject(normalizedObject);
    }

    private static JObject SortPropertiesAlphabetically(JObject original)
    {
        var result = new JObject();

        foreach (var property in original.Properties().ToList().OrderBy(p => p.Name))
        {
            var value = property.Value as JObject;

            if (value != null)
            {
                value = SortPropertiesAlphabetically(value);
                result.Add(property.Name, value);
            }
            else
            {
                result.Add(property.Name, property.Value);
            }
        }

        return result;
    }
}
Niaher
la source
2
C'était le correctif requis pour nous lors de l'utilisation de dictionnaires.
noocyte
Cela ajoute une surcharge de désérialisation et de sérialisation supplémentaires. J'ai ajouté une solution qui fonctionnera également pour les classes normales, les dictionnaires et ExpandoObject (objet dynamique)
Jay Shah
11

Dans mon cas, la solution de niaher n'a pas fonctionné car elle ne gérait pas les objets dans les tableaux.

Sur la base de sa solution, c'est ce que j'ai proposé

public static class JsonUtility
{
    public static string NormalizeJsonString(string json)
    {
        JToken parsed = JToken.Parse(json);

        JToken normalized = NormalizeToken(parsed);

        return JsonConvert.SerializeObject(normalized);
    }

    private static JToken NormalizeToken(JToken token)
    {
        JObject o;
        JArray array;
        if ((o = token as JObject) != null)
        {
            List<JProperty> orderedProperties = new List<JProperty>(o.Properties());
            orderedProperties.Sort(delegate(JProperty x, JProperty y) { return x.Name.CompareTo(y.Name); });
            JObject normalized = new JObject();
            foreach (JProperty property in orderedProperties)
            {
                normalized.Add(property.Name, NormalizeToken(property.Value));
            }
            return normalized;
        }
        else if ((array = token as JArray) != null)
        {
            for (int i = 0; i < array.Count; i++)
            {
                array[i] = NormalizeToken(array[i]);
            }
            return array;
        }
        else
        {
            return token;
        }
    }
}
Tuan-Tu Tran
la source
Cela ajoute une surcharge de désérialisation et de sérialisation supplémentaires.
Jay Shah
Excellente solution. Je vous remercie.
MaYaN
3

Comme Charlie l'a noté, vous pouvez quelque peu contrôler l'ordre des propriétés JSON en ordonnant les propriétés dans la classe elle-même. Malheureusement, cette approche ne fonctionne pas pour les propriétés héritées d'une classe de base. Les propriétés de la classe de base seront classées telles qu'elles sont présentées dans le code, mais apparaîtront avant les propriétés de la classe de base.

Et pour tous ceux qui se demandent pourquoi vous souhaitez classer par ordre alphabétique les propriétés JSON, il est beaucoup plus facile de travailler avec des fichiers JSON bruts, en particulier pour les classes avec de nombreuses propriétés, si elles sont ordonnées.

Jack Bond
la source
2

Cela fonctionnera également pour les classes normales, les dictionnaires et ExpandoObject (objet dynamique).

class OrderedPropertiesContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(System.Type type, MemberSerialization memberSerialization)
        {
            var props = base.CreateProperties(type, memberSerialization);
            return props.OrderBy(p => p.PropertyName).ToList();
        }
    }



class OrderedExpandoPropertiesConverter : ExpandoObjectConverter
    {
        public override bool CanWrite
        {
            get { return true; }
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var expando = (IDictionary<string, object>)value;
            var orderedDictionary = expando.OrderBy(x => x.Key).ToDictionary(t => t.Key, t => t.Value);
            serializer.Serialize(writer, orderedDictionary);
        }
    }



var settings = new JsonSerializerSettings
        {
            ContractResolver = new OrderedPropertiesContractResolver(),
            Converters = { new OrderedExpandoPropertiesConverter() }
        };

var serializedString = JsonConvert.SerializeObject(obj, settings);
Jay Shah
la source
N'était-ce pas le comportement de classement par défaut lors de la sérialisation?
mr5
1
Pour économiser à quelqu'un d'autre quelques minutes perdues, notez que cette réponse ne fonctionne pas pour les dictionnaires malgré la revendication. CreatePropertiesn'est pas appelée lors de la sérialisation d'un dictionnaire. J'ai exploré le repo JSON.net pour savoir quelles machines sont réellement en train de parcourir les entrées du dictionnaire. Il ne se connecte à aucune overridepersonnalisation pour la commande. Il prend simplement les entrées telles quelles de l'énumérateur de l'objet. Il semble que je doive construire un SortedDictionaryou SortedListpour forcer JSON.net à le faire. Suggestion de fonctionnalité déposée: github.com/JamesNK/Newtonsoft.Json/issues/2270
William
2

Si vous ne voulez pas mettre un JsonProperty Orderattribut sur chaque propriété de classe, il est très simple de créer votre propre ContractResolver ...

L'interface IContractResolver fournit un moyen de personnaliser la façon dont JsonSerializer sérialise et désérialise les objets .NET en JSON sans placer d'attributs sur vos classes.

Comme ça:

private class SortedPropertiesContractResolver : DefaultContractResolver
{
    // use a static instance for optimal performance
    static SortedPropertiesContractResolver instance;

    static SortedPropertiesContractResolver() { instance = new SortedPropertiesContractResolver(); }

    public static SortedPropertiesContractResolver Instance { get { return instance; } }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);
        if (properties != null)
            return properties.OrderBy(p => p.UnderlyingName).ToList();
        return properties;
    }
}

Mettre en place:

var settings = new JsonSerializerSettings { ContractResolver = SortedPropertiesContractResolver.Instance };
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
CrazyTim
la source
0

La méthode récursive suivante utilise la réflexion pour trier la liste de jetons interne sur une JObjectinstance existante plutôt que de créer un tout nouveau graphique d'objets triés. Ce code s'appuie sur les détails d'implémentation Json.NET internes et ne doit pas être utilisé en production.

void SortProperties(JToken token)
{
    var obj = token as JObject;
    if (obj != null)
    {
        var props = typeof (JObject)
            .GetField("_properties",
                      BindingFlags.NonPublic | BindingFlags.Instance)
            .GetValue(obj);
        var items = typeof (Collection<JToken>)
            .GetField("items", BindingFlags.NonPublic | BindingFlags.Instance)
            .GetValue(props);
        ArrayList.Adapter((IList) items)
            .Sort(new ComparisonComparer(
                (x, y) =>
                {
                    var xProp = x as JProperty;
                    var yProp = y as JProperty;
                    return xProp != null && yProp != null
                        ? string.Compare(xProp.Name, yProp.Name)
                        : 0;
                }));
    }
    foreach (var child in token.Children())
    {
        SortProperties(child);
    }
}
Nathan Baulch
la source
0

En fait, comme mon Object était déjà un JObject, j'ai utilisé la solution suivante:

public class SortedJObject : JObject
{
    public SortedJObject(JObject other)
    {
        var pairs = new List<KeyValuePair<string, JToken>>();
        foreach (var pair in other)
        {
            pairs.Add(pair);
        }
        pairs.OrderBy(p => p.Key).ForEach(pair => this[pair.Key] = pair.Value);
    }
}

puis utilisez-le comme ceci:

string serializedObj = JsonConvert.SerializeObject(new SortedJObject(dataObject));
Danny R
la source
0

Si vous contrôlez (c'est-à-dire écrivez) la classe, placez les propriétés par ordre alphabétique et elles seront sérialisées par ordre alphabétique lors de l' JsonConvert.SerializeObject()appel.

Charlie
la source
0

Je veux sérialiser un objet comblex et conserver l'ordre des propriétés telles qu'elles ont été définies dans le code. Je ne peux pas simplement ajouter [JsonProperty(Order = 1)]parce que la classe elle-même est hors de ma portée.

Cette solution prend également en compte le fait que les propriétés définies dans une classe de base doivent avoir une priorité plus élevée.

Cela n'est peut-être pas à toute épreuve, car nulle part n'est défini qui MetaDataAttributegarantit le bon ordre, mais cela semble fonctionner. Pour mon cas d'utilisation, c'est ok. puisque je veux seulement maintenir la lisibilité humaine pour un fichier de configuration généré automatiquement.

public class PersonWithAge : Person
{
    public int Age { get; set; }
}

public class Person
{
    public string Name { get; set; }
}

public string GetJson()
{
    var thequeen = new PersonWithAge { Name = "Elisabeth", Age = Int32.MaxValue };

    var settings = new JsonSerializerSettings()
    {
        ContractResolver = new MetadataTokenContractResolver(),
    };

    return JsonConvert.SerializeObject(
        thequeen, Newtonsoft.Json.Formatting.Indented, settings
    );

}

public class MetadataTokenContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(
        Type type, MemberSerialization memberSerialization)
    {
        var props = type
           .GetProperties(BindingFlags.Instance
               | BindingFlags.Public
               | BindingFlags.NonPublic
           ).ToDictionary(k => k.Name, v =>
           {
               // first value: declaring type
               var classIndex = 0;
               var t = type;
               while (t != v.DeclaringType)
               {
                   classIndex++;
                   t = type.BaseType;
               }
               return Tuple.Create(classIndex, v.MetadataToken);
           });

        return base.CreateProperties(type, memberSerialization)
            .OrderByDescending(p => props[p.PropertyName].Item1)
            .ThenBy(p => props[p.PropertyName].Item1)
            .ToList();
    }
}

Jürgen Steinblock
la source
-1

Si vous souhaitez configurer globalement votre API avec des champs ordonnés, veuillez combiner la réponse de Mattias Nordberg:

public class OrderedContractResolver : DefaultContractResolver
{
    protected override System.Collections.Generic.IList<JsonProperty> CreateProperties(System.Type type, MemberSerialization memberSerialization)
    {
        return base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName).ToList();
    }
}

avec ma réponse ici:

Comment forcer l'API Web ASP.NET à toujours renvoyer JSON?

Carlo Saccone
la source
-5

METTRE À JOUR

Je viens de voir les votes négatifs. Veuillez consulter la réponse de «Steve» ci-dessous pour savoir comment procéder.

ORIGINAL

J'ai suivi l' JsonConvert.SerializeObject(key)appel de méthode via la réflexion (où la clé était un IList) et j'ai trouvé que JsonSerializerInternalWriter.SerializeList était appelé. Il prend une liste et boucle via

for (int i = 0; i < values.Count; i++) { ...

où values ​​est le paramètre IList introduit.

La réponse courte est ... Non, il n'y a pas de moyen intégré de définir l'ordre dans lequel les champs sont répertoriés dans la chaîne JSON.

DougJones
la source
18
Réponse courte, mais peut-être dépassée. Découvrez la réponse de Steve (soutenu par James Newton-king)
Brad Bruce
-6

Il n'y a pas d'ordre des champs au format JSON, donc définir un ordre n'a pas de sens.

{ id: 1, name: 'John' }est équivalent à { name: 'John', id: 1 }(les deux représentent une instance d'objet strictement équivalente)

Darin Dimitrov
la source
12
@Darin - mais il y a un ordre dans la sérialisation. "{id: 1, name: 'John'}" et "{name: 'John', id: 1}" sont différents en tant que chaînes , c'est ce qui m'intéresse ici. Bien entendu, les objets sont équivalents lorsqu'ils sont désérialisés.
Kevin Montrose
1
@Darin - non, pas dans ce cas. Je sérialise quelque chose, puis je le transmets sous forme de chaîne à un service qui ne traite que des chaînes (pas compatible JSON), et il serait pratique pour diverses raisons qu'un champ apparaisse en premier dans la chaîne.
Kevin Montrose
1
c'est bon pour les tests aussi, pouvoir simplement regarder les chaînes plutôt que d'avoir à désérialiser.
Steve
9
Un ordre de sérialisation stable est également pratique pour la validation du cache. Il est trivial de prendre une somme de contrôle d'une chaîne - pas vrai pour un graphe d'objets complet.
solublefish
1
L'ordre de sérialisation est également pratique lorsque vous effectuez des tests unitaires afin que vous puissiez facilement dire que les chaînes de réponse attendues et réelles sont égales même lorsque l'ordre des propriétés json est différent.
Anon