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.


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


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

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

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

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.


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

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

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

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

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 .


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


-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?


-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.


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)


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.