Kolejność pól serializowanych za pomocą JSON.NET

137

Czy istnieje sposób na określenie kolejności pól w zserializowanym obiekcie JSON przy użyciu JSON.NET ?

Wystarczyłoby określić, że jedno pole zawsze pojawia się jako pierwsze.

Kevin Montrose
źródło
7
myślę, że prawdopodobnie jest zainteresowany wyświetleniem najpierw pola ID (lub podobnego), a następnie wszystkich innych pól. jest to bardziej przyjazne dla użytkowników końcowych niż szukanie go po polach zaczynających się na literę A..I
Michael Bahig 14.12
3
Właściwości JSON są zdefiniowane jako nieuporządkowane. Myślę, że wymuszenie określonej kolejności WYJŚCIA podczas serializacji (być może w celu obejrzenia JSON) jest absolutnie w porządku, ale utworzenie ZALEŻNOŚCI od konkretnego zamówienia podczas deserializacji byłoby złą decyzją.
DaBlick
5
Kilka ważnych powodów: (1) udawanie właściwości „$ type”, która musi być pierwszą właściwością w JSON, (2) próba wygenerowania JSON, który kompresuje jak najwięcej
Stephen Chung,
4
Innym powodem może być (3) reprezentacja kanoniczna, która używa składni JSON - ten sam obiekt musi mieć gwarancję, że wygeneruje ten sam ciąg JSON. Warunkiem koniecznym jest deterministyczny porządek atrybutów.
MarkusSchaber
2
Kevin, czy możesz zaktualizować zaakceptowaną odpowiedź na to pytanie?
Millie Smith

Odpowiedzi:

255

Obsługiwanym sposobem jest użycie JsonPropertyatrybutu we właściwościach klasy, dla których chcesz ustawić kolejność. Przeczytaj dokumentację zamówienia JsonPropertyAttribute, aby uzyskać więcej informacji.

Zdać JsonPropertysię Orderwartość i serializer zajmie się resztą.

 [JsonProperty(Order = 1)]

Jest to bardzo podobne do

 DataMember(Order = 1) 

z System.Runtime.Serializationdni.

Oto ważna uwaga od @ kevin-babcock

... ustawienie kolejności na 1 zadziała tylko wtedy, gdy ustawisz kolejność większą niż 1 dla wszystkich innych właściwości. Domyślnie każda właściwość bez ustawienia Order otrzyma kolejność -1. Musisz więc podać wszystkie zserializowane właściwości i kolejność lub ustawić pierwszy element na -2

Steve
źródło
97
Za pomocą Orderwłaściwości JsonPropertyAttributemożna kontrolować kolejność serializacji / deserializacji pól. Jednak ustawienie kolejności na 1 zadziała tylko wtedy, gdy ustawisz kolejność większą niż 1 dla wszystkich innych właściwości. Domyślnie każda właściwość bez ustawienia Order otrzyma kolejność -1. Musisz więc podać wszystkie zserializowane właściwości i kolejność lub ustawić pierwszy element na -2.
Kevin Babcock,
1
Działa w przypadku serializacji, ale kolejność nie jest brana pod uwagę podczas deserializacji. Zgodnie z dokumentacją atrybut order jest używany zarówno do serializacji, jak i deserializacji. Czy jest w pobliżu praca?
cangosta
1
Czy istnieje podobna właściwość dla JavaScriptSerializer.
Shimmy Weitzhandler
4
@cangosta Kolejność deserializacji nie powinna mieć znaczenia .. z wyjątkiem niektórych bardzo „dziwnych” przypadków oczekiwań.
user2864740
1
Przeczytaj podobną dyskusję na githubie dotyczącą pragnienia poszanowania Zakonu w deserializacji: github.com/JamesNK/Newtonsoft.Json/issues/758 W zasadzie nie ma na to szans.
Tyeth
126

Rzeczywiście można kontrolować kolejność poprzez wdrożenie IContractResolverlub nadpisanie DefaultContractResolver„s CreatePropertiesmetody.

Oto przykład mojej prostej implementacji, IContractResolverktóra porządkuje właściwości alfabetycznie:

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

A następnie ustaw ustawienia i serializuj obiekt, a pola JSON będą w kolejności alfabetycznej:

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

var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
Mattias Nordberg
źródło
11
Jest to całkiem pomocne (+1), ale jedno zastrzeżenie: wydaje się, że serializacja słowników nie korzysta z tej personalizacji CreateProperties. Serializują się dobrze, ale nie są sortowane. Zakładam, że istnieje inny sposób dostosowania serializacji słowników, ale go nie znalazłem.
rozpuszczalna ryba
Idealny. Robi to, co chciałem. Dzięki.
Wade Hatler
To świetne rozwiązanie. U mnie zadziałało idealnie, zwłaszcza gdy umieściłem 2 obiekty JSON obok siebie i wyrównałem właściwości.
Vince
16

W moim przypadku odpowiedź Mattiasa nie zadziałała. CreatePropertiesMetoda nie została wywołana.

Po pewnym debugowaniu elementów Newtonsoft.Jsonwewnętrznych wpadłem na inne rozwiązanie.

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
źródło
2
To była wymagana poprawka dla nas podczas korzystania z dyktowania.
noocyte
Zwiększa to narzut dodatkowej deserializacji i serializacji. Dodałem rozwiązanie, które będzie działać dla normalnych klas, słowników i ExpandoObject (dynamiczny obiekt)
Jay Shah
11

W moim przypadku rozwiązanie niaher nie zadziałało, ponieważ nie obsługiwało obiektów w tablicach.

Na podstawie jego rozwiązania wymyśliłem to

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
źródło
Zwiększa to narzut dodatkowej deserializacji i serializacji.
Jay Shah
Doskonałe rozwiązanie. Dziękuję Ci.
MaYaN
3

Jak zauważył Charlie, możesz w pewnym stopniu kontrolować kolejność właściwości JSON, porządkując właściwości w samej klasie. Niestety to podejście nie działa w przypadku właściwości dziedziczonych z klasy bazowej. Właściwości klasy bazowej zostaną uporządkowane zgodnie z układem w kodzie, ale pojawią się przed właściwościami klasy bazowej.

A dla każdego, kto zastanawia się, dlaczego warto ułożyć alfabetycznie właściwości JSON, praca z surowymi plikami JSON jest o wiele łatwiejsza, szczególnie w przypadku klas z dużą ilością właściwości, jeśli są one uporządkowane.

Jack Bond
źródło
2

Będzie to działać również dla normalnych klas, słowników i ExpandoObject (obiektu dynamicznego).

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
źródło
Czy nie było to domyślne zachowanie kolejności podczas serializacji?
mr5
1
Aby zaoszczędzić komuś kilka straconych minut, pamiętaj, że ta odpowiedź nie działa w przypadku słowników pomimo twierdzenia. CreatePropertiesnie jest wywoływana podczas serializacji słownika. Przeszukałem repozytorium JSON.net, aby sprawdzić, jakie maszyny faktycznie przeglądają wpisy słownika. Nie wiąże się z żadnym overridedostosowaniem do zamówienia. Po prostu pobiera wpisy takie, jakie są z modułu wyliczającego obiektu. Wygląda na to, że muszę skonstruować SortedDictionarylub SortedListzmusić JSON.net do zrobienia tego. Propozycja funkcji zgłoszona: github.com/JamesNK/Newtonsoft.Json/issues/2270
William
2

Jeśli nie chcesz umieszczać JsonProperty Orderatrybutu na każdej właściwości klasy, bardzo łatwo jest utworzyć własny element ContractResolver ...

Interfejs IContractResolver umożliwia dostosowanie sposobu serializacji i deserializacji obiektów .NET do formatu JSON przez JsonSerializer bez umieszczania atrybutów w klasach.

Lubię to:

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

Wprowadzić w życie:

var settings = new JsonSerializerSettings { ContractResolver = SortedPropertiesContractResolver.Instance };
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
CrazyTim
źródło
0

Poniższa metoda rekurencyjna używa odbicia do sortowania wewnętrznej listy tokenów w istniejącej JObjectinstancji zamiast tworzenia zupełnie nowego posortowanego wykresu obiektów. Ten kod opiera się na wewnętrznych szczegółach implementacji Json.NET i nie powinien być używany w środowisku produkcyjnym.

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
źródło
0

Właściwie, ponieważ mój Object był już JObject, zastosowałem następujące rozwiązanie:

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

a następnie użyj tego w ten sposób:

string serializedObj = JsonConvert.SerializeObject(new SortedJObject(dataObject));
Danny R.
źródło
0

Jeśli kontrolujesz (tj. Piszesz) klasę, umieść właściwości w porządku alfabetycznym, a po JsonConvert.SerializeObject()wywołaniu zostaną one serializowane w kolejności alfabetycznej .

Charlie
źródło
0

Chcę serializować obiekt comblex i zachować kolejność właściwości zgodnie z definicją w kodzie. Nie mogę po prostu dodać, [JsonProperty(Order = 1)]ponieważ sama klasa jest poza moim zakresem.

To rozwiązanie uwzględnia również, że właściwości zdefiniowane w klasie bazowej powinny mieć wyższy priorytet.

Może to nie być kuloodporne, ponieważ nigdzie nie określono, że MetaDataAttributezapewnia prawidłową kolejność, ale wydaje się, że działa. W moim przypadku jest to w porządku. ponieważ chcę zachować czytelność tylko dla człowieka dla automatycznie generowanego pliku konfiguracyjnego.

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
źródło
-1

Jeśli chcesz globalnie skonfigurować swoje API z uporządkowanymi polami, połącz odpowiedź Mattiasa Nordberga:

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

z moją odpowiedzią tutaj:

Jak zmusić interfejs API sieci Web ASP.NET, aby zawsze zwracał JSON?

Carlo Saccone
źródło
-5

AKTUALIZACJA

Właśnie zobaczyłem głosy przeciw. Zobacz odpowiedź od „Steve” poniżej, aby dowiedzieć się, jak to zrobić.

ORYGINALNY

Śledziłem JsonConvert.SerializeObject(key)wywołanie metody przez odbicie (gdzie klucz był IList) i stwierdziłem, że JsonSerializerInternalWriter.SerializeList jest wywoływana. Pobiera listę i przechodzi przez

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

gdzie wartości to wprowadzony parametr IList.

Krótka odpowiedź brzmi ... Nie, nie ma wbudowanego sposobu ustawiania kolejności, w jakiej pola są wyświetlane w ciągu JSON.

DougJones
źródło
18
Krótka odpowiedź, ale prawdopodobnie nieaktualna. Sprawdź odpowiedź Steve'a (wspieraną przez Jamesa Newtona-króla)
Brad Bruce
-6

Nie ma kolejności pól w formacie JSON, więc definiowanie kolejności nie ma sensu.

{ id: 1, name: 'John' }jest równoważne { name: 'John', id: 1 }(oba reprezentują ściśle równoważną instancję obiektu)

Darin Dimitrov
źródło
12
@Darin - ale w serializacji jest kolejność. „{id: 1, name: 'John'}” i „{name: 'John', id: 1}” są różne jak ciągi znaków i właśnie na tym mi zależy. Oczywiście obiekty są równoważne po deserializacji.
Kevin Montrose
1
@Darin - nie, nie w tym przypadku. Serializuję coś, a następnie przekazuję to jako ciąg do usługi, która zajmuje się tylko ciągami znaków (bez uwzględnienia formatu JSON) i byłoby wygodne z różnych powodów, aby jedno pole pojawiało się jako pierwsze w ciągu.
Kevin Montrose
1
jest również dobry do testowania, będąc w stanie po prostu spojrzeć na łańcuchy, zamiast konieczności deserializacji.
Steve
9
Stabilna kolejność serializacji jest również przydatna do sprawdzania poprawności pamięci podręcznej. Pobranie sumy kontrolnej łańcucha jest trywialne - nie jest to prawda dla pełnego grafu obiektów.
rozpuszczalna ryba
1
Kolejność serializacji jest również przydatna podczas wykonywania testów jednostkowych, dzięki czemu można łatwo stwierdzić, że oczekiwane i rzeczywiste ciągi odpowiedzi są równe, nawet jeśli kolejność właściwości json jest inna.
anon