Замовлення серіалізованих полів за допомогою JSON.NET


137

Чи є спосіб вказати порядок полів у серіалізованому об'єкті JSON за допомогою JSON.NET ?

Достатньо було б вказати, що одне поле завжди з’являється першим.


7
я думаю, що він, ймовірно, зацікавлений у тому, щоб спочатку показати поле ID (або подібне), а потім усі інші поля. це більш
приємно

3
Властивості JSON визначаються як не упорядковані. Я думаю, що цілком нормально примусово виконувати певний порядок виходу під час серіалізації (можливо, заради очного яблука JSON), але було б поганим рішенням створити ЗАВИСНІСТЬ у будь-якому конкретному порядку про десеріалізацію.
DaBlick

5
Кілька поважних причин: (1) підробка властивості "$ type", яка повинна бути першою властивістю в JSON, (2) намагання генерувати JSON, який максимально стискає
Stephen Chung

4
Ще однією причиною може бути (3) канонічне подання, яке використовує синтаксис JSON - той самий об'єкт повинен бути гарантований для отримання тієї ж рядки JSON. Детермінований порядок атрибутів є необхідною умовою цього.
MarkusSchaber

2
Кевін, ти можеш оновити прийняту відповідь на це питання?
Міллі Сміт

Відповіді:


255

Підтримуваний спосіб полягає у використанні JsonPropertyатрибута для властивостей класу, для якого потрібно встановити порядок. Прочитайте документацію замовлення JsonPropertyAttribute для отримання додаткової інформації.

Пропускають JsonPropertyв Orderзначення і серіалізатор подбає про решту.

 [JsonProperty(Order = 1)]

Це дуже схоже на

 DataMember(Order = 1) 

з System.Runtime.Serializationднів.

Ось важлива примітка від @ kevin-babcock

... встановлення порядку до 1 буде працювати лише в тому випадку, якщо для всіх інших властивостей встановлено замовлення, що перевищує 1. За замовчуванням будь-якій властивості без налаштування замовлення буде надано порядок -1. Отже, ви повинні або надати всі серіалізовані властивості та замовити, або встановити свій перший елемент на -2


97
Використання Orderвластивості JsonPropertyAttributecan може використовуватися для контролю порядку, в якому поля серіалізуються / дезаріалізуються. Однак встановлення порядку до 1 буде працювати лише в тому випадку, якщо для всіх інших властивостей встановлено порядок більше 1. За замовчуванням будь-якій властивості без налаштування замовлення буде надано порядок -1. Отже, ви повинні або надати всі серіалізовані властивості та замовити, або встановити свій перший елемент на -2.
Кевін Бабкок

1
Він працює для серіалізації, але наказ про десеріалізацію не розглядається. Згідно з документацією, атрибут замовлення використовується як для серіалізації, так і для дезаріалізації. Чи існує рішення?
кангоста

1
Чи є схожа властивість для JavaScriptSerializer.
Шиммі Вайцхандлер

4
@cangosta Порядок десеріалізації не повинен мати значення .. за винятком деяких дуже "незвичайних" випадків очікування.
user2864740

1
Прочитайте подібну дискусію з питань github навколо прагнення до поваги Порядку в десеріалізації: github.com/JamesNK/Newtonsoft.Json/isissue/758 В основному шансу на це немає.
Тиєт

126

Ви можете фактично контролювати замовлення, застосовуючи IContractResolverабо переосмислюючи метод DefaultContractResolver's CreateProperties.

Ось приклад моєї простої реалізації, IContractResolverяка впорядковує властивості за алфавітом:

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

А потім встановіть параметри та серіалізуйте об’єкт, і поля JSON будуть в алфавітному порядку:

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

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

11
Це дуже корисно (+1), але одне застереження: схоже, серіалізація словників не використовує цю настройку CreateProperties. Вони добре серіалізуються, але не закінчуються сортованими. Я припускаю, що існує інший спосіб налаштувати серіалізацію словників, але я не знайшов його.
розчинна риба

Ідеально. Робить саме те, що я хотів. Дякую.
Уейд Хатлер

Це чудове рішення. Особливо добре працював для мене, особливо коли 2 JSON-об'єкти ставили поряд і з властивостями вирівнювались.
Вінс

16

У моєму випадку відповідь Маттіаса не спрацювала. CreatePropertiesМетод ніколи не викликається.

Після деяких налагоджень Newtonsoft.Jsonвнутрішніх справ я придумав інше рішення.

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
Це було необхідне нам виправлення при використанні диктів.
нооцит

Це додає накладних витрат на додаткову десеріалізацію та серіалізацію. Я додав рішення, яке також буде працювати для звичайних класів, словників та ExpandoObject (динамічний об’єкт)
Jay Shah

11

У моєму випадку рішення niaher не спрацювало, оскільки воно не обробляло об'єкти в масивах.

На основі його рішення це те, що я придумав

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

Це додає накладних витрат на додаткову десеріалізацію та серіалізацію.
Джей Шах

Відмінне рішення. Дякую.
МАЙЯ

3

Як зазначав Чарлі, ви можете дещо контролювати впорядкування властивостей JSON, упорядковуючи властивості у самому класі. На жаль, цей підхід не працює для властивостей, успадкованих від базового класу. Властивості базового класу будуть упорядковані так, як вони викладені в коді, але з'являться перед властивостями базового класу.

А для тих, хто цікавиться, чому ви можете з алфавіту властивостей JSON, набагато простіше працювати з сирими файлами JSON, особливо для класів з великою кількістю властивостей, якщо вони замовлені.


2

Це також буде працювати для звичайних класів, словників та ExpandoObject (динамічний об’єкт).

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

Хіба це не поведінка щодо замовлення за замовчуванням під час серіалізації?
mr5

1
Щоб заощадити комусь ще кілька витрачених хвилин, зауважте, що ця відповідь не працює для словників, незважаючи на твердження. CreatePropertiesне викликається під час серіалізації словника. Я досліджував репортаж JSON.net щодо того, яка техніка насправді лускає через записи словника. Він не підключається до будь-якої overrideчи іншої настройки для замовлення. Він просто приймає записи такими, якими вони є, з обчислювача об'єкта. Здається, я повинен побудувати SortedDictionaryабо SortedListзмусити JSON.net зробити це. Подано
Вільям

2

Якщо ви не хочете вводити JsonProperty Orderатрибут у власність кожного класу, то зробити його власним ContractResolver дуже просто ...

Інтерфейс IContractResolver надає спосіб налаштувати, як JsonSerializer серіалізує та десеріалізує .NET-об’єкти до JSON, не розміщуючи атрибути у ваших класах.

Подобається це:

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

Реалізація:

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

0

Наступний рекурсивний метод використовує відображення для сортування внутрішнього списку токенів у існуючому JObjectекземплярі, а не для створення абсолютно нового відсортованого графіка об'єкта. Цей код покладається на внутрішні деталі реалізації Json.NET і не повинен використовуватися у виробництві.

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

Насправді, оскільки мій Об’єкт вже був JObject, я застосував таке рішення:

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

а потім використовуйте його так:

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

0

Якщо ви керуєте (тобто записуєте) клас, поставте властивості в алфавітному порядку, і вони будуть серіалізувати в алфавітному порядку, коли JsonConvert.SerializeObject()викликається.


0

Я хочу серіалізувати об’єкт comblex і підтримувати порядок властивостей так, як вони визначені в коді. Я не можу просто додати, [JsonProperty(Order = 1)]тому що сам клас поза моєю сферою.

Це рішення також враховує, що властивості, визначені в базовому класі, повинні мати більш високий пріоритет.

Це не може бути куленепроникним, оскільки ніде не визначено, що MetaDataAttributeзабезпечує правильний порядок, але, здається, працює. Для мого випадку це нормально. оскільки я хочу лише підтримувати читабельність людини для автоматично створеного конфігураційного файла.

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

Якщо ви хочете глобально налаштувати свій API з упорядкованими полями, будь ласка, поєднайте відповідь 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();
    }
}

з моєю відповіддю тут:

Як змусити веб-API ASP.NET завжди повертати JSON?


-5

ОНОВЛЕННЯ

Я щойно бачив низини. Будь ласка, дивіться відповідь "Стіва" нижче, як це зробити.

ОРИГІНАЛЬНИЙ

Я прослідкував за JsonConvert.SerializeObject(key)викликом методу через відображення (де ключовим був IList) і виявив, що JsonSerializerInternalWriter.SerializeList викликає. Він бере список і проходить цикл через via

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

де значення - введений параметр IList.

Коротка відповідь - Ні, немає вбудованого способу встановлення порядку, в якому поля перераховані в рядку JSON.


18
Коротка відповідь, але можливо застаріла. Перевірте відповідь Стіва (підтримується Джеймсом Ньютоном-королем)
Бред Брюс

-6

Немає порядку полів у форматі JSON, тому визначення порядку не має сенсу.

{ id: 1, name: 'John' }еквівалентний { name: 'John', id: 1 }(обидва являють собою суто еквівалентний об'єкт)


12
@Darin - але в серіалізації є порядок. "{id: 1, name: 'John'}" та "{name: 'John', id: 1}" різні як рядки , про що я тут хвилююся. Звичайно, об'єкти рівноцінні при десеріалізації.
Кевін Монтроуз

1
@Darin - ні, не в цьому випадку. Я щось серіалізую, а потім передаю його як рядок до сервісу, який працює лише в рядках (не знає JSON), і було б зручно з різних причин, щоб одне поле з’явилося першим у рядку.
Кевін Монтроуз

1
це добре і для тестування, що він може просто дивитись на рядки, а не десаріалізувати.
Стів

9
Стабільне замовлення на серіалізацію також зручно для перевірки кешу. Тривіально взяти контрольну суму рядка - не вірно з повним графіком об'єкта.
розчинна риба

1
Порядок серіалізації також є зручним під час тестування одиниць, так що ви можете легко сказати, що очікувані проти фактичних рядків відповіді рівні, навіть коли порядок властивостей json відрізняється.
Anon
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.