Можно ли указать порядок полей в сериализованном объекте JSON, используя JSON.NET?
Достаточно указать, что одно поле всегда появляется первым.
Можно ли указать порядок полей в сериализованном объекте JSON, используя JSON.NET?
Достаточно указать, что одно поле всегда появляется первым.
Я следил за вызовом метода JsonConvert.SerializeObject(key)
через отражение (где ключ был IList) и обнаружил, что вызван JsonSerializerInternalWriter.SerializeList. Он принимает список и проходит через
for (int i = 0; i < values.Count; i++) { ...
где значения - это параметр IList.
Короткий ответ: Нет, нет встроенного способа установить порядок, в котором поля указаны в строке JSON.
Поддерживаемый способ - использовать атрибут JsonProperty
для свойств класса, для которого вы хотите установить порядок. Подробнее см. документацию по заказу JsonPropertyAttribute.
Передайте значение JsonProperty
an Order
, и сериализатор позаботится об остальном.
[JsonProperty(Order = 1)]
Это очень похоже на
DataMember(Order = 1)
дней System.Runtime.Serialization
.
Фактически вы можете управлять порядком, реализуя IContractResolver
или переопределяя метод DefaultContractResolver
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, особенно для классов с большим количеством свойств, если они упорядочены.
Нет порядка полей в формате JSON, поэтому определение порядка не имеет смысла.
{ id: 1, name: 'John' }
эквивалентен { name: 'John', id: 1 }
(оба представляют собой строго эквивалентный экземпляр объекта)
Следующий рекурсивный метод использует отражение для сортировки внутреннего списка токенов в существующем экземпляре 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));
Если вы управляете (то есть записываете) класс, поместите свойства в алфавитном порядке, и они будут сериализованы в алфавитном порядке, когда вызывается JsonConvert.SerializeObject()
.