Чи є спосіб вказати порядок полів у серіалізованому об'єкті JSON за допомогою JSON.NET ?
Достатньо було б вказати, що одне поле завжди з’являється першим.
Чи є спосіб вказати порядок полів у серіалізованому об'єкті JSON за допомогою JSON.NET ?
Достатньо було б вказати, що одне поле завжди з’являється першим.
Відповіді:
Підтримуваний спосіб полягає у використанні JsonProperty
атрибута для властивостей класу, для якого потрібно встановити порядок. Прочитайте документацію замовлення JsonPropertyAttribute для отримання додаткової інформації.
Пропускають JsonProperty
в Order
значення і серіалізатор подбає про решту.
[JsonProperty(Order = 1)]
Це дуже схоже на
DataMember(Order = 1)
з System.Runtime.Serialization
днів.
Ось важлива примітка від @ kevin-babcock
... встановлення порядку до 1 буде працювати лише в тому випадку, якщо для всіх інших властивостей встановлено замовлення, що перевищує 1. За замовчуванням будь-якій властивості без налаштування замовлення буде надано порядок -1. Отже, ви повинні або надати всі серіалізовані властивості та замовити, або встановити свій перший елемент на -2
Order
властивості JsonPropertyAttribute
can може використовуватися для контролю порядку, в якому поля серіалізуються / дезаріалізуються. Однак встановлення порядку до 1 буде працювати лише в тому випадку, якщо для всіх інших властивостей встановлено порядок більше 1. За замовчуванням будь-якій властивості без налаштування замовлення буде надано порядок -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);
}
}
Насправді, оскільки мій Об’єкт вже був 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 викликає. Він бере список і проходить цикл через via
for (int i = 0; i < values.Count; i++) { ...
де значення - введений параметр IList.
Коротка відповідь - Ні, немає вбудованого способу встановлення порядку, в якому поля перераховані в рядку JSON.
Немає порядку полів у форматі JSON, тому визначення порядку не має сенсу.
{ id: 1, name: 'John' }
еквівалентний { name: 'John', id: 1 }
(обидва являють собою суто еквівалентний об'єкт)