Порядок сериализованных полей с использованием JSON.NET

138

Есть ли способ указать порядок полей в сериализованном объекте JSON, используя JSON.NET ?

Было бы достаточно указать, что одно поле всегда появляется первым.

Кевин Монтроз
источник
7
я думаю, что он, вероятно, заинтересован в том, чтобы сначала показать поле идентификатора (или подобное), а затем все остальные поля. это удобнее для конечных пользователей, чем искать его после полей, начинающихся с A..I
Майкл Бахиг
3
Свойства JSON определены как неупорядоченные. Я думаю, что абсолютно нормально навязывать конкретный порядок OUTPUT во время сериализации (возможно, ради наглядного представления JSON), но было бы плохим решением создать ЗАВИСИМОСТЬ от любого конкретного порядка десериализации.
DaBlick
5
Пара веских причин: (1) фальсификация свойства «$ type», которое должно быть первым свойством в JSON, (2) попытка сгенерировать JSON, который сжимает как можно больше
Стивен Чунг,
4
Другой причиной может быть (3) каноническое представление, которое использует синтаксис JSON - один и тот же объект должен гарантированно генерировать одну и ту же строку JSON. Детерминированный порядок атрибутов является необходимой предпосылкой для этого.
Маркус Шабер
2
Кевин, можешь ли ты обновить принятый ответ на этот вопрос?
Милли Смит

Ответы:

256

Поддерживаемый способ - использовать JsonPropertyатрибут в свойствах класса, для которых вы хотите установить порядок. Прочтите документацию заказа JsonPropertyAttribute для получения дополнительной информации.

Пропускают JsonPropertyв Orderзначение и сериализатору позаботится об остальном.

 [JsonProperty(Order = 1)]

Это очень похоже на

 DataMember(Order = 1) 

из System.Runtime.Serializationдней.

Вот важное замечание от @ kevin-babcock

... установка порядка 1 будет работать, только если вы установите порядок больше 1 для всех других свойств. По умолчанию любое свойство без параметра Order будет иметь порядок -1. Таким образом, вы должны либо дать все сериализованные свойства и порядок, либо установить свой первый элемент на -2

Стив
источник
97
Использование Orderсвойства JsonPropertyAttributeможет использоваться для управления порядком, в котором поля сериализуются / десериализуются. Однако установка порядка 1 будет работать, только если вы установите порядок больше 1 для всех других свойств. По умолчанию любое свойство без параметра Order будет иметь порядок -1. Таким образом, вы должны либо дать все сериализованные свойства и порядок, либо установить свой первый элемент на -2.
Кевин Бэбкок
1
Он работает для сериализации, но порядок десериализации не рассматривается. Согласно документации, атрибут order используется как для сериализации, так и для десериализации. Есть ли обходной путь?
Кангоста
1
Есть ли подобное свойство для JavaScriptSerializer.
Shimmy Weitzhandler
4
@cangosta Порядок десериализации не должен иметь значения .. за исключением некоторых очень «странных» случаев ожидания.
user2864740
1
Прочитайте аналогичную дискуссию о проблемах GitHub вокруг желания, чтобы Order был уважаем при десериализации: github.com/JamesNK/Newtonsoft.Json/issues/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;
    }
}
niaher
источник
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);
CrazyTim
источник
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

На самом деле, поскольку мой Object уже был 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();
    }
}

с моим ответом здесь:

Как заставить ASP.NET Web API всегда возвращать JSON?

Карло Сакконе
источник
-5

ОБНОВИТЬ

Я только что видел отрицательные голоса. Пожалуйста, посмотрите ответ «Стива» ниже, чтобы узнать, как это сделать.

ОРИГИНАЛ

Я проследил за JsonConvert.SerializeObject(key)вызовом метода через отражение (где ключом был IList) и обнаружил, что вызывается JsonSerializerInternalWriter.SerializeList. Он берет список и перебирает через

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

где values ​​- это введенный параметр 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