Czy istnieje sposób na określenie kolejności pól w zserializowanym obiekcie JSON przy użyciu JSON.NET ?
Wystarczyłoby określić, że jedno pole zawsze pojawia się jako pierwsze.
Czy istnieje sposób na określenie kolejności pól w zserializowanym obiekcie JSON przy użyciu JSON.NET ?
Wystarczyłoby określić, że jedno pole zawsze pojawia się jako pierwsze.
Odpowiedzi:
Obsługiwanym sposobem jest użycie JsonProperty
atrybutu we właściwościach klasy, dla których chcesz ustawić kolejność. Przeczytaj dokumentację zamówienia JsonPropertyAttribute, aby uzyskać więcej informacji.
Zdać JsonProperty
się Order
wartość i serializer zajmie się resztą.
[JsonProperty(Order = 1)]
Jest to bardzo podobne do
DataMember(Order = 1)
z System.Runtime.Serialization
dni.
Oto ważna uwaga od @ kevin-babcock
... ustawienie kolejności na 1 zadziała tylko wtedy, gdy ustawisz kolejność większą niż 1 dla wszystkich innych właściwości. Domyślnie każda właściwość bez ustawienia Order otrzyma kolejność -1. Musisz więc podać wszystkie zserializowane właściwości i kolejność lub ustawić pierwszy element na -2
Order
właściwości JsonPropertyAttribute
można kontrolować kolejność serializacji / deserializacji pól. Jednak ustawienie kolejności na 1 zadziała tylko wtedy, gdy ustawisz kolejność większą niż 1 dla wszystkich innych właściwości. Domyślnie każda właściwość bez ustawienia Order otrzyma kolejność -1. Musisz więc podać wszystkie zserializowane właściwości i kolejność lub ustawić pierwszy element na -2.
JavaScriptSerializer
.
Rzeczywiście można kontrolować kolejność poprzez wdrożenie IContractResolver
lub nadpisanie DefaultContractResolver
„s CreateProperties
metody.
Oto przykład mojej prostej implementacji, IContractResolver
która porządkuje właściwości alfabetycznie:
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();
}
}
A następnie ustaw ustawienia i serializuj obiekt, a pola JSON będą w kolejności alfabetycznej:
var settings = new JsonSerializerSettings()
{
ContractResolver = new OrderedContractResolver()
};
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
W moim przypadku odpowiedź Mattiasa nie zadziałała. CreateProperties
Metoda nie została wywołana.
Po pewnym debugowaniu elementów Newtonsoft.Json
wewnętrznych wpadłem na inne rozwiązanie.
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;
}
}
W moim przypadku rozwiązanie niaher nie zadziałało, ponieważ nie obsługiwało obiektów w tablicach.
Na podstawie jego rozwiązania wymyśliłem to
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;
}
}
}
Jak zauważył Charlie, możesz w pewnym stopniu kontrolować kolejność właściwości JSON, porządkując właściwości w samej klasie. Niestety to podejście nie działa w przypadku właściwości dziedziczonych z klasy bazowej. Właściwości klasy bazowej zostaną uporządkowane zgodnie z układem w kodzie, ale pojawią się przed właściwościami klasy bazowej.
A dla każdego, kto zastanawia się, dlaczego warto ułożyć alfabetycznie właściwości JSON, praca z surowymi plikami JSON jest o wiele łatwiejsza, szczególnie w przypadku klas z dużą ilością właściwości, jeśli są one uporządkowane.
Będzie to działać również dla normalnych klas, słowników i ExpandoObject (obiektu dynamicznego).
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
nie jest wywoływana podczas serializacji słownika. Przeszukałem repozytorium JSON.net, aby sprawdzić, jakie maszyny faktycznie przeglądają wpisy słownika. Nie wiąże się z żadnym override
dostosowaniem do zamówienia. Po prostu pobiera wpisy takie, jakie są z modułu wyliczającego obiektu. Wygląda na to, że muszę skonstruować SortedDictionary
lub SortedList
zmusić JSON.net do zrobienia tego. Propozycja funkcji zgłoszona: github.com/JamesNK/Newtonsoft.Json/issues/2270
Jeśli nie chcesz umieszczać JsonProperty
Order
atrybutu na każdej właściwości klasy, bardzo łatwo jest utworzyć własny element ContractResolver ...
Interfejs IContractResolver umożliwia dostosowanie sposobu serializacji i deserializacji obiektów .NET do formatu JSON przez JsonSerializer bez umieszczania atrybutów w klasach.
Lubię to:
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;
}
}
Wprowadzić w życie:
var settings = new JsonSerializerSettings { ContractResolver = SortedPropertiesContractResolver.Instance };
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
Poniższa metoda rekurencyjna używa odbicia do sortowania wewnętrznej listy tokenów w istniejącej JObject
instancji zamiast tworzenia zupełnie nowego posortowanego wykresu obiektów. Ten kod opiera się na wewnętrznych szczegółach implementacji Json.NET i nie powinien być używany w środowisku produkcyjnym.
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);
}
}
Właściwie, ponieważ mój Object był już JObject, zastosowałem następujące rozwiązanie:
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);
}
}
a następnie użyj tego w ten sposób:
string serializedObj = JsonConvert.SerializeObject(new SortedJObject(dataObject));
Chcę serializować obiekt comblex i zachować kolejność właściwości zgodnie z definicją w kodzie. Nie mogę po prostu dodać, [JsonProperty(Order = 1)]
ponieważ sama klasa jest poza moim zakresem.
To rozwiązanie uwzględnia również, że właściwości zdefiniowane w klasie bazowej powinny mieć wyższy priorytet.
Może to nie być kuloodporne, ponieważ nigdzie nie określono, że MetaDataAttribute
zapewnia prawidłową kolejność, ale wydaje się, że działa. W moim przypadku jest to w porządku. ponieważ chcę zachować czytelność tylko dla człowieka dla automatycznie generowanego pliku konfiguracyjnego.
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();
}
}
Jeśli chcesz globalnie skonfigurować swoje API z uporządkowanymi polami, połącz odpowiedź Mattiasa Nordberga:
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();
}
}
z moją odpowiedzią tutaj:
Jak zmusić interfejs API sieci Web ASP.NET, aby zawsze zwracał JSON?
AKTUALIZACJA
Właśnie zobaczyłem głosy przeciw. Zobacz odpowiedź od „Steve” poniżej, aby dowiedzieć się, jak to zrobić.
ORYGINALNY
Śledziłem JsonConvert.SerializeObject(key)
wywołanie metody przez odbicie (gdzie klucz był IList) i stwierdziłem, że JsonSerializerInternalWriter.SerializeList jest wywoływana. Pobiera listę i przechodzi przez
for (int i = 0; i < values.Count; i++) { ...
gdzie wartości to wprowadzony parametr IList.
Krótka odpowiedź brzmi ... Nie, nie ma wbudowanego sposobu ustawiania kolejności, w jakiej pola są wyświetlane w ciągu JSON.
Nie ma kolejności pól w formacie JSON, więc definiowanie kolejności nie ma sensu.
{ id: 1, name: 'John' }
jest równoważne { name: 'John', id: 1 }
(oba reprezentują ściśle równoważną instancję obiektu)