Ordine dei campi serializzati utilizzando JSON.NET


138

C'è un modo per specificare l'ordine dei campi in un oggetto JSON serializzato usando JSON.NET ?

Sarebbe sufficiente specificare che un singolo campo appare sempre per primo.


7
penso che sia probabilmente interessato a mostrare prima il campo ID (o simile) e poi tutti gli altri campi. questo è più amichevole per gli utenti finali piuttosto che cercarlo dopo i campi che iniziano con A..I
Michael Bahig

3
Le proprietà JSON sono definite come non ordinate. Penso che sia assolutamente corretto forzare un particolare ordine OUTPUT durante la serializzazione (per motivi di bulbo oculare forse JSON), ma sarebbe una cattiva decisione creare una DIPENDENZA su qualsiasi ordine particolare sulla deserializzazione.
DaBlick,

5
Un paio di validi motivi: (1) falsificare una proprietà "$ type" che deve essere la prima proprietà in JSON, (2) cercare di generare JSON che comprime il più possibile
Stephen Chung,

4
Un altro motivo potrebbe essere (3) una rappresentazione canonica che utilizza la sintassi JSON: lo stesso oggetto deve essere garantito per produrre la stessa stringa JSON. Un ordine deterministico degli attributi è un prerequisito necessario per questo.
MarkusSchaber

2
Kevin, puoi aggiornare la risposta accettata su questa domanda?
Millie Smith,

Risposte:


256

Il modo supportato è utilizzare l' JsonPropertyattributo sulle proprietà della classe per cui si desidera impostare l'ordine. Leggi la documentazione dell'ordine di JsonPropertyAttribute per ulteriori informazioni.

Passa il valore JsonPropertyan Ordere il serializzatore si occuperà del resto.

 [JsonProperty(Order = 1)]

Questo è molto simile al

 DataMember(Order = 1) 

dei System.Runtime.Serializationgiorni.

Ecco una nota importante di @ kevin-babcock

... impostando l'ordine su 1 funzionerà solo se imposti un ordine maggiore di 1 su tutte le altre proprietà. Per impostazione predefinita, a qualsiasi proprietà senza impostazione Ordine verrà assegnato un ordine di -1. Quindi devi dare tutte le proprietà e l'ordine serializzati oppure impostare il tuo primo oggetto su -2


97
L'utilizzo della Orderproprietà di JsonPropertyAttributepuò essere utilizzato per controllare l'ordine in cui i campi sono serializzati / deserializzati. Tuttavia, l'impostazione dell'ordine su 1 funzionerà solo se si imposta un ordine maggiore di 1 su tutte le altre proprietà. Per impostazione predefinita, a qualsiasi proprietà senza impostazione Ordine verrà assegnato un ordine di -1. Quindi devi dare tutte le proprietà e l'ordine serializzati oppure impostare il tuo primo oggetto su -2.
Kevin Babcock,

1
Funziona per la serializzazione, ma l'ordine non viene preso in considerazione per la deserializzazione. Secondo la documentazione, l'attributo order viene utilizzato sia per la serializzazione che per la deserializzazione. C'è una soluzione?
Cangosta,

1
Esiste una proprietà simile per il JavaScriptSerializer.
Shimmy Weitzhandler il

4
@cangosta L'ordine per la deserializzazione non dovrebbe avere importanza ... tranne in alcuni casi di aspettativa molto "dispari".
user2864740

1
Leggi le discussioni sui problemi di github simili sul desiderio che l'Ordine sia rispettato nella deserializzazione: github.com/JamesNK/Newtonsoft.Json/issues/758 Praticamente nessuna possibilità di questo.
Tyeth,

126

Si può effettivamente controllare l'ordine mediante l'attuazione IContractResolvero l'override del DefaultContractResolver's CreatePropertiesmetodo.

Ecco un esempio della mia semplice implementazione di IContractResolvercui ordina le proprietà in ordine alfabetico:

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

Quindi imposta le impostazioni e serializza l'oggetto, ei campi JSON saranno in ordine alfabetico:

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

var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);

11
Questo è abbastanza utile (+1) ma un avvertimento: sembra che la serializzazione dei dizionari non usi questa personalizzazione di CreateProperties. Serializzano bene ma non finiscono per essere ordinati. Suppongo che ci sia un modo diverso di personalizzare la serializzazione dei dizionari, ma non l'ho trovato.
pesce solubile

Perfetto. Fa proprio quello che volevo. Grazie.
Wade Hatler,

Questa è un'ottima soluzione Ha funzionato perfettamente per me soprattutto quando ho messo 2 oggetti JSON fianco a fianco e ho allineato le proprietà.
Vince,

16

Nel mio caso la risposta di Mattias non ha funzionato. Il CreatePropertiesmetodo non è mai stato chiamato.

Dopo un po 'di debug degli Newtonsoft.Jsoninterni, ho trovato un'altra soluzione.

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

2
Questa è stata la correzione necessaria per noi quando si utilizza dicts.
noocyte,

Ciò aggiunge il sovraccarico di ulteriore deserializzazione e serializzazione. Ho aggiunto una soluzione che funzionerà anche per normali classi, dizionari e ExpandoObject (oggetto dinamico)
Jay Shah,

11

Nel mio caso la soluzione di niaher non ha funzionato perché non ha gestito gli oggetti negli array.

Sulla base della sua soluzione, questo è quello che mi è venuto in mente

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

Ciò aggiunge il sovraccarico di ulteriore deserializzazione e serializzazione.
Jay Shah,

Soluzione eccellente Grazie.
MaYaN,

3

Come notato da Charlie, puoi in qualche modo controllare l'ordinamento delle proprietà JSON ordinando le proprietà nella classe stessa. Sfortunatamente, questo approccio non funziona per le proprietà ereditate da una classe base. Le proprietà della classe base saranno ordinate come sono disposte nel codice, ma appariranno prima delle proprietà della classe base.

E per chiunque si chieda perché potresti voler alfabetizzare le proprietà JSON, è molto più semplice lavorare con file JSON non elaborati, in particolare per le classi con molte proprietà, se sono ordinate.


2

Funzionerà anche per normali classi, dizionari e ExpandoObject (oggetto dinamico).

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

Non era questo il comportamento di ordinazione predefinito durante la serializzazione?
sig.

1
Per risparmiare qualcun altro qualche minuto sprecato, nota che questa risposta non funziona per i dizionari nonostante l'affermazione. CreatePropertiesnon viene richiamato durante la serializzazione di un dizionario. Ho esplorato il repository JSON.net per quale macchinario sta effettivamente analizzando le voci del dizionario. Non si aggancia a nessuna overrideo altra personalizzazione per l'ordinazione. Prende le voci così come sono dall'enumeratore dell'oggetto. Sembra che debba costruire un SortedDictionaryo SortedListforzare JSON.net a farlo. Suggerimento di funzionalità archiviato: github.com/JamesNK/Newtonsoft.Json/issues/2270
William

2

Se non vuoi mettere un JsonProperty Orderattributo su ogni proprietà della classe, è molto semplice creare il tuo ContractResolver ...

L'interfaccia IContractResolver fornisce un modo per personalizzare il modo in cui JsonSerializer serializza e deserializza gli oggetti .NET su JSON senza posizionare gli attributi nelle classi.

Come questo:

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

Strumento:

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

0

Il seguente metodo ricorsivo utilizza la riflessione per ordinare l'elenco di token interno su JObjectun'istanza esistente anziché creare un nuovo grafico di oggetti ordinati. Questo codice si basa sui dettagli interni dell'implementazione di Json.NET e non deve essere utilizzato in produzione.

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

0

In realtà, poiché il mio oggetto era già un JObject, ho usato la seguente soluzione:

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

e poi usalo in questo modo:

string serializedObj = JsonConvert.SerializeObject(new SortedJObject(dataObject));

0

Se controlli (cioè scrivi) la classe, metti le proprietà in ordine alfabetico e queste verranno serializzate in ordine alfabetico quando JsonConvert.SerializeObject()viene chiamato.


0

Voglio serializzare un oggetto comblex e mantenere l'ordine delle proprietà come sono state definite nel codice. Non posso semplicemente aggiungere [JsonProperty(Order = 1)]perché la classe stessa è fuori dal mio ambito.

Questa soluzione tiene anche conto del fatto che le proprietà definite in una classe base dovrebbero avere una priorità più alta.

Questo potrebbe non essere a prova di proiettile, poiché da nessuna parte è definito che MetaDataAttributegarantisce l'ordine corretto, ma sembra funzionare. Per il mio caso d'uso questo è ok. poiché voglio solo mantenere la leggibilità umana per un file di configurazione generato automaticamente.

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


-1

Se desideri configurare globalmente la tua API con i campi ordinati, combina la risposta di 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();
    }
}

con la mia risposta qui:

Come forzare l'API Web ASP.NET per restituire sempre JSON?


-5

AGGIORNARE

Ho appena visto i voti negativi. Si prega di vedere la risposta da 'Steve' di seguito per come fare.

ORIGINALE

Ho seguito la JsonConvert.SerializeObject(key)chiamata del metodo tramite reflection (dove key era un IList) e ho scoperto che viene chiamato JsonSerializerInternalWriter.SerializeList. Prende un elenco e scorre attraverso

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

dove valori è il parametro IList introdotto.

La risposta breve è ... No, non esiste un modo integrato per impostare l'ordine in cui i campi sono elencati nella stringa JSON.


18
Risposta breve, ma forse obsoleta. Guarda la risposta di Steve (supportata da James Newton-king)
Brad Bruce il

-6

Non esiste un ordine di campi nel formato JSON, quindi la definizione di un ordine non ha senso.

{ id: 1, name: 'John' }è equivalente a { name: 'John', id: 1 }(entrambi rappresentano un'istanza di oggetto strettamente equivalente)


12
@Darin - ma c'è un ordine nella serializzazione. "{id: 1, name: 'John'}" e "{name: 'John', id: 1}" sono diversi come stringhe , che è ciò che mi interessa qui. Naturalmente, gli oggetti sono equivalenti quando sono deserializzati.
Kevin Montrose

1
@Darin - no, non in questo caso. Sto serializzando qualcosa e poi lo passo come una stringa a un servizio che si occupa solo di stringhe (non consapevole di JSON), e sarebbe conveniente per una varietà di motivi per cui un campo appare prima nella stringa.
Kevin Montrose

1
è utile anche per i test, essere in grado di guardare solo le stringhe anziché dover deserializzare.
Steve,

9
Un ordine di serializzazione stabile è utile anche per la convalida della cache. È banale prendere un checksum di una stringa - non vero per un grafico a oggetti completo.
pesce solubile

1
L'ordine di serializzazione è utile anche quando si eseguono test unitari in modo da poter dire facilmente che le stringhe di risposta attese / effettive sono uguali anche quando l'ordine delle proprietà json è diverso.
anon
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.