Есть ли способ указать порядок полей в сериализованном объекте JSON, используя JSON.NET ?
Было бы достаточно указать, что одно поле всегда появляется первым.
Есть ли способ указать порядок полей в сериализованном объекте JSON, используя JSON.NET ?
Было бы достаточно указать, что одно поле всегда появляется первым.
Ответы:
Поддерживаемый способ - использовать JsonProperty
атрибут в свойствах класса, для которых вы хотите установить порядок. Прочтите документацию заказа JsonPropertyAttribute для получения дополнительной информации.
Пропускают JsonProperty
в Order
значение и сериализатору позаботится об остальном.
[JsonProperty(Order = 1)]
Это очень похоже на
DataMember(Order = 1)
из System.Runtime.Serialization
дней.
Вот важное замечание от @ kevin-babcock
... установка порядка 1 будет работать, только если вы установите порядок больше 1 для всех других свойств. По умолчанию любое свойство без параметра Order будет иметь порядок -1. Таким образом, вы должны либо дать все сериализованные свойства и порядок, либо установить свой первый элемент на -2
Order
свойства JsonPropertyAttribute
может использоваться для управления порядком, в котором поля сериализуются / десериализуются. Однако установка порядка 1 будет работать, только если вы установите порядок больше 1 для всех других свойств. По умолчанию любое свойство без параметра Order будет иметь порядок -1. Таким образом, вы должны либо дать все сериализованные свойства и порядок, либо установить свой первый элемент на -2.
JavaScriptSerializer
.
Вы можете фактически контролировать порядок путем осуществления 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);
В моем случае ответ Маттиаса не сработал. 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 не сработало, потому что оно не обрабатывало объекты в массивах.
Исходя из его решения, это то, что я придумал
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;
}
}
}
Как заметил Чарли, вы можете в некоторой степени контролировать порядок свойств JSON, упорядочивая свойства в самом классе. К сожалению, этот подход не работает для свойств, унаследованных от базового класса. Свойства базового класса будут упорядочены так, как они выложены в коде, но появятся перед свойствами базового класса.
И для всех, кто задается вопросом, почему вам может понадобиться расположить по алфавиту свойства JSON, гораздо проще работать с необработанными файлами JSON, особенно для классов с большим количеством свойств, если они упорядочены.
Это будет работать и для обычных классов, словарей и 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);
CreateProperties
не вызывается во время сериализации словаря. Я изучил репозиторий JSON.net, чтобы выяснить, какие механизмы действительно работают с записями словаря. Он не привязывается к какой-либо override
или другой настройке для заказа. Он просто принимает записи как есть из перечислителя объекта. Кажется, мне нужно создать SortedDictionary
или SortedList
заставить JSON.net сделать это. Подано предложение о добавлении
Если вы не хотите помещать 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);
Следующий рекурсивный метод использует отражение для сортировки внутреннего списка токенов в существующем 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);
}
}
На самом деле, поскольку мой 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));
Я хочу сериализовать объект 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();
}
}
Если вы хотите глобально настроить свой 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();
}
}
с моим ответом здесь:
ОБНОВИТЬ
Я только что видел отрицательные голоса. Пожалуйста, посмотрите ответ «Стива» ниже, чтобы узнать, как это сделать.
ОРИГИНАЛ
Я проследил за JsonConvert.SerializeObject(key)
вызовом метода через отражение (где ключом был IList) и обнаружил, что вызывается JsonSerializerInternalWriter.SerializeList. Он берет список и перебирает через
for (int i = 0; i < values.Count; i++) { ...
где values - это введенный параметр IList.
Краткий ответ ... Нет, нет встроенного способа установить порядок, в котором поля перечислены в строке JSON.
Там нет порядка полей в формате JSON, поэтому определение порядка не имеет смысла.
{ id: 1, name: 'John' }
эквивалентно { name: 'John', id: 1 }
(оба представляют строго эквивалентный экземпляр объекта)