Reihenfolge der serialisierten Felder mit JSON.NET

137

Gibt es eine Möglichkeit, die Reihenfolge der Felder in einem serialisierten JSON-Objekt mithilfe von JSON.NET anzugeben ?

Es würde ausreichen anzugeben, dass immer zuerst ein einzelnes Feld angezeigt wird.

Kevin Montrose
quelle
7
Ich denke, er ist wahrscheinlich daran interessiert, zuerst das ID-Feld (oder ähnliches) und dann alle anderen Felder anzuzeigen. Dies ist für Endbenutzer freundlicher, als nach Feldern zu suchen, die mit A..I beginnen
Michael Bahig
3
JSON-Eigenschaften werden als ungeordnet definiert. Ich denke, es ist absolut in Ordnung, eine bestimmte OUTPUT-Reihenfolge während der Serialisierung zu erzwingen (vielleicht um den JSON im Auge zu behalten), aber es wäre eine schlechte Entscheidung, eine ABHÄNGIGKEIT für eine bestimmte Reihenfolge bei der Deserialisierung zu erstellen.
DaBlick
5
Ein paar triftige Gründe: (1) Fälschung einer "$ type" -Eigenschaft, die die erste Eigenschaft im JSON sein muss, (2) Versuch, JSON zu generieren, das so viel wie möglich komprimiert
Stephen Chung,
4
Ein weiterer Grund könnte (3) eine kanonische Darstellung sein, die die JSON-Syntax verwendet. Es muss garantiert werden, dass dasselbe Objekt dieselbe JSON-Zeichenfolge erzeugt. Eine deterministische Reihenfolge der Attribute ist hierfür eine notwendige Voraussetzung.
MarkusSchaber
2
Kevin, kannst du die akzeptierte Antwort auf diese Frage aktualisieren?
Millie Smith

Antworten:

255

Die unterstützte Methode besteht darin, das JsonPropertyAttribut für die Klasseneigenschaften zu verwenden, für die Sie die Reihenfolge festlegen möchten. Weitere Informationen finden Sie in der Bestelldokumentation zu JsonPropertyAttribute .

Übergeben Sie den JsonPropertyein OrderWert und der Serializer wird der Rest kümmern.

 [JsonProperty(Order = 1)]

Dies ist sehr ähnlich zu

 DataMember(Order = 1) 

der System.Runtime.SerializationTage.

Hier ist ein wichtiger Hinweis von @ kevin-babcock

... das Setzen der Reihenfolge auf 1 funktioniert nur, wenn Sie für alle anderen Eigenschaften eine Reihenfolge größer als 1 festlegen. Standardmäßig erhält jede Eigenschaft ohne Auftragseinstellung den Auftrag -1. Sie müssen also entweder alle serialisierten Eigenschaften und die Reihenfolge angeben oder Ihren ersten Artikel auf -2 setzen

Steve
quelle
97
Mithilfe der OrderEigenschaft von JsonPropertyAttributekann die Reihenfolge gesteuert werden, in der Felder serialisiert / deserialisiert werden. Das Festlegen der Reihenfolge auf 1 funktioniert jedoch nur, wenn Sie für alle anderen Eigenschaften eine Reihenfolge größer als 1 festlegen. Standardmäßig erhält jede Eigenschaft ohne Auftragseinstellung den Auftrag -1. Sie müssen also entweder alle serialisierten Eigenschaften und die Reihenfolge angeben oder Ihren ersten Artikel auf -2 setzen.
Kevin Babcock
1
Es funktioniert für die Serialisierung, aber die Reihenfolge wird bei der Deserialisierung nicht berücksichtigt. Gemäß der Dokumentation wird das Auftragsattribut sowohl für die Serialisierung als auch für die Deserialisierung verwendet. Gibt es eine Problemumgehung?
Cangosta
Gibt es eine ähnliche Eigenschaft für die JavaScriptSerializer.
Shimmy Weitzhandler
3
@cangosta Die Reihenfolge der Deserialisierung sollte keine Rolle spielen. Außer in einigen sehr "seltsamen" Erwartungsfällen.
user2864740
Lesen Sie die ähnliche Diskussion über Github-Themen rund um den Wunsch, dass die Ordnung bei der Deserialisierung respektiert wird: github.com/JamesNK/Newtonsoft.Json/issues/758 Grundsätzlich keine Chance für diese.
Tyeth
125

Sie können steuern , tatsächlich die Reihenfolge durch die Implementierung IContractResolveroder das übergeordnete DefaultContractResolver‚s - CreatePropertiesMethode.

Hier ist ein Beispiel für meine einfache Implementierung, bei IContractResolverder die Eigenschaften alphabetisch geordnet sind:

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

Legen Sie dann die Einstellungen fest und serialisieren Sie das Objekt. Die JSON-Felder werden in alphabetischer Reihenfolge angezeigt:

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

var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
Mattias Nordberg
quelle
11
Dies ist sehr hilfreich (+1), aber eine Einschränkung: Es scheint, dass die Serialisierung von Wörterbüchern diese Anpassung von CreateProperties nicht verwendet. Sie serialisieren gut, werden aber nicht sortiert. Ich gehe davon aus, dass es eine andere Möglichkeit gibt, die Serialisierung von Wörterbüchern anzupassen, aber ich habe sie nicht gefunden.
löslicher Fisch
Perfekt. Macht genau das, was ich wollte. Vielen Dank.
Wade Hatler
Dies ist eine großartige Lösung. Funktionierte perfekt für mich, insbesondere wenn 2 JSON-Objekte nebeneinander platziert und die Eigenschaften ausgerichtet wurden.
Vince
16

In meinem Fall hat Mattias 'Antwort nicht funktioniert. Die CreatePropertiesMethode wurde nie aufgerufen.

Nach einigem Debuggen von Newtonsoft.JsonInterna habe ich eine andere Lösung gefunden.

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
quelle
2
Dies war die erforderliche Lösung für uns bei der Verwendung von Diktaten.
Noocyte
Dies erhöht den Aufwand für zusätzliche Deserialisierung und Serialisierung. Ich habe eine Lösung hinzugefügt, die auch für normale Klassen, Wörterbücher und ExpandoObject (dynamisches Objekt) funktioniert
Jay Shah
11

In meinem Fall hat die Lösung von niaher nicht funktioniert, da sie keine Objekte in Arrays verarbeitet hat.

Basierend auf seiner Lösung habe ich mir das ausgedacht

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
quelle
Dies erhöht den Aufwand für zusätzliche Deserialisierung und Serialisierung.
Jay Shah
Hervorragende Lösung. Danke dir.
MaYaN
3

Wie Charlie bemerkte, können Sie die Reihenfolge der JSON-Eigenschaften etwas steuern, indem Sie die Eigenschaften in der Klasse selbst anordnen. Leider funktioniert dieser Ansatz nicht für Eigenschaften, die von einer Basisklasse geerbt wurden. Die Eigenschaften der Basisklasse werden so angeordnet, wie sie im Code angeordnet sind, werden jedoch vor den Eigenschaften der Basisklasse angezeigt.

Und für alle, die sich fragen, warum Sie JSON-Eigenschaften alphabetisch sortieren möchten, ist es viel einfacher, mit JSON-Rohdateien zu arbeiten, insbesondere für Klassen mit vielen Eigenschaften, wenn diese geordnet sind.

Jack Bond
quelle
2

Dies funktioniert auch für normale Klassen, Wörterbücher und ExpandoObject (dynamisches Objekt).

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
quelle
War dies nicht das Standardbestellverhalten während der Serialisierung?
mr5
1
Beachten Sie, dass diese Antwort trotz der Behauptung für Wörterbücher nicht funktioniert, um jemand anderem ein paar verschwendete Minuten zu ersparen. CreatePropertieswird während der Serialisierung eines Wörterbuchs nicht aufgerufen. Ich habe das JSON.net-Repo untersucht, um herauszufinden, welche Maschinen tatsächlich durch die Wörterbucheinträge tuckern. Es lässt sich nicht in eine overrideoder andere Anpassung für die Bestellung einbinden. Es werden nur die Einträge wie sie sind aus dem Enumerator des Objekts übernommen. Es scheint, dass ich ein SortedDictionaryoder SortedListerstellen muss, um JSON.net dazu zu zwingen. Feature-Vorschlag eingereicht: github.com/JamesNK/Newtonsoft.Json/issues/2270
William
2

Wenn Sie nicht JsonProperty Orderjeder Klasseneigenschaft ein Attribut zuweisen möchten, können Sie ganz einfach Ihren eigenen ContractResolver erstellen ...

Über die IContractResolver-Schnittstelle können Sie anpassen, wie der JsonSerializer .NET-Objekte in JSON serialisiert und deserialisiert, ohne Attribute für Ihre Klassen zu platzieren.

So was:

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

Implementieren:

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

Die folgende rekursive Methode verwendet Reflektion, um die interne Token-Liste für eine vorhandene JObjectInstanz zu sortieren, anstatt ein brandneues sortiertes Objektdiagramm zu erstellen. Dieser Code basiert auf internen Json.NET-Implementierungsdetails und sollte nicht in der Produktion verwendet werden.

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
quelle
0

Da mein Objekt bereits ein JObject war, habe ich die folgende Lösung verwendet:

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

und dann benutze es so:

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

Wenn Sie die Klasse steuern (dh schreiben), ordnen Sie die Eigenschaften in alphabetischer Reihenfolge an, und sie werden beim JsonConvert.SerializeObject()Aufruf in alphabetischer Reihenfolge serialisiert .

Charlie
quelle
0

Ich möchte ein kombiniertes Objekt serialisieren und die Reihenfolge der Eigenschaften beibehalten, wie sie im Code definiert wurden. Ich kann nicht einfach hinzufügen, [JsonProperty(Order = 1)]da die Klasse selbst außerhalb meines Bereichs liegt.

Diese Lösung berücksichtigt auch, dass Eigenschaften, die in einer Basisklasse definiert sind, eine höhere Priorität haben sollten.

Dies ist möglicherweise nicht kugelsicher, da nirgends definiert ist, dass MetaDataAttributedie richtige Reihenfolge gewährleistet ist, aber es scheint zu funktionieren. Für meinen Anwendungsfall ist dies in Ordnung. da ich nur die menschliche Lesbarkeit für eine automatisch generierte Konfigurationsdatei beibehalten möchte.

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
quelle
-1

Wenn Sie Ihre API global mit geordneten Feldern konfigurieren möchten, kombinieren Sie bitte die Antwort von 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();
    }
}

mit meiner Antwort hier:

Wie kann ich die ASP.NET-Web-API zwingen, immer JSON zurückzugeben?

Carlo Saccone
quelle
-5

AKTUALISIEREN

Ich habe gerade die Abstimmungen gesehen. Bitte lesen Sie die Antwort von 'Steve' unten, wie das geht.

ORIGINAL

Ich folgte dem JsonConvert.SerializeObject(key)Methodenaufruf über Reflection (wobei der Schlüssel eine IList war) und stellte fest, dass JsonSerializerInternalWriter.SerializeList aufgerufen wird. Es nimmt eine Liste und durchläuft via

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

Dabei ist values ​​der eingegebene IList-Parameter.

Die kurze Antwort lautet ... Nein, es gibt keine integrierte Möglichkeit, die Reihenfolge festzulegen, in der die Felder in der JSON-Zeichenfolge aufgeführt sind.

DougJones
quelle
18
Kurze Antwort, aber möglicherweise veraltet. Schauen Sie sich Steves Antwort an (unterstützt von James Newton-king)
Brad Bruce
-6

Es gibt keine Reihenfolge der Felder im JSON-Format, daher ist das Definieren einer Reihenfolge nicht sinnvoll.

{ id: 1, name: 'John' }ist äquivalent zu { name: 'John', id: 1 }(beide repräsentieren eine streng äquivalente Objektinstanz)

Darin Dimitrov
quelle
12
@Darin - aber es gibt eine Reihenfolge in der Serialisierung. "{id: 1, name: 'John'}" und "{name: 'John', id: 1}" unterscheiden sich als Zeichenfolgen , was mir hier wichtig ist. Natürlich sind die Objekte beim Deserialisieren gleichwertig.
Kevin Montrose
1
@Darin - nein, in diesem Fall nicht. Ich serialisiere etwas und gebe es dann als Zeichenfolge an einen Dienst weiter, der nur Zeichenfolgen verarbeitet (nicht JSON-fähig). Aus verschiedenen Gründen wäre es praktisch, wenn ein Feld zuerst in der Zeichenfolge angezeigt wird.
Kevin Montrose
1
Es ist auch gut zum Testen, da man nur die Saiten betrachten kann, anstatt sie deserialisieren zu müssen.
Steve
9
Eine stabile Serialisierungsreihenfolge ist auch für die Cache-Validierung praktisch. Es ist trivial, eine Prüfsumme einer Zeichenfolge zu verwenden - nicht für ein vollständiges Objektdiagramm.
löslicher Fisch
1
Die Serialisierungsreihenfolge ist auch praktisch, wenn Unit-Tests durchgeführt werden, sodass Sie leicht sagen können, dass die erwarteten und tatsächlichen Antwortzeichenfolgen auch dann gleich sind, wenn die Reihenfolge der json-Eigenschaften unterschiedlich ist.
Anon