Existe-t-il un moyen de spécifier l'ordre des champs dans un objet JSON sérialisé à l'aide de JSON.NET ?
Il suffirait de spécifier qu'un seul champ apparaît toujours en premier.
La méthode prise en charge consiste à utiliser l'attribut JsonProperty
sur les propriétés de classe pour lesquelles vous souhaitez définir l'ordre. Lisez la documentation relative à la commande JsonPropertyAttribute pour plus d'informations.
Passez la valeur JsonProperty
une Order
et le sérialiseur se chargera du reste.
[JsonProperty(Order = 1)]
Ceci est très similaire à la
DataMember(Order = 1)
des System.Runtime.Serialization
jours.
Voici une note importante de @ kevin-babcock
... définir l'ordre à 1 ne fonctionnera que si vous définissez un ordre supérieur à 1 sur toutes les autres propriétés. Par défaut, toute propriété sans paramètre Ordre recevra un ordre de -1. Vous devez donc soit donner toutes les propriétés et les commandes sérialisées, soit définir le premier élément sur -2.
Vous pouvez réellement contrôler l'ordre en implémentant IContractResolver
ou en redéfinissant la méthode DefaultContractResolver
's CreateProperties
.
Voici un exemple de mon implémentation simple de IContractResolver
qui classe les propriétés par ordre alphabétique:
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();
}
}
Et puis définissez les paramètres et sérialisez l'objet, et les champs JSON seront dans l'ordre alphabétique:
var settings = new JsonSerializerSettings()
{
ContractResolver = new OrderedContractResolver()
};
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
Dans mon cas, la réponse de Mattias n'a pas fonctionné. La méthode CreateProperties
n'a jamais été appelée.
Après un certain débogage des internes de Newtonsoft.Json
, j'ai proposé une autre solution.
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;
}
}
Dans mon cas, la solution de niaher ne fonctionnait pas car elle ne gérait pas les objets dans les tableaux.
Basé sur sa solution c'est ce que je suis venu avec
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;
}
}
}
Comme Charlie l'a noté, vous pouvez contrôler en quelque sorte l'ordre des propriétés JSON en les ordonnant dans la classe elle-même. Malheureusement, cette approche ne fonctionne pas pour les propriétés héritées d'une classe de base. Les propriétés de la classe de base seront ordonnées telles qu'elles sont définies dans le code, mais apparaîtront avant les propriétés de la classe de base.
Et pour ceux qui se demandent pourquoi vous pourriez vouloir alphabétiser les propriétés JSON, il est beaucoup plus facile de travailler avec des fichiers JSON bruts, en particulier pour les classes comportant de nombreuses propriétés, si elles sont ordonnées.
Cela fonctionnera également pour les classes normales, les dictionnaires et ExpandoObject (objet dynamique).
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);
En fait, mon objet étant déjà un objet JO, j'ai utilisé la solution suivante:
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);
}
}
et ensuite l'utiliser comme ça:
string serializedObj = JsonConvert.SerializeObject(new SortedJObject(dataObject));
Je veux sérialiser un objet Comblex et conserver l'ordre des propriétés telles qu'elles ont été définies dans le code. Je ne peux pas simplement ajouter [JsonProperty(Order = 1)]
car la classe elle-même est hors de portée.
Cette solution prend également en compte le fait que les propriétés définies dans une classe de base doivent avoir une priorité plus élevée.
Cela peut ne pas être à l'épreuve des balles, car il n'est défini nulle part que la variable MetaDataAttribute
garantit le bon ordre, mais cela semble fonctionner. Pour mon cas d'utilisation c'est ok. puisque je veux seulement maintenir la lisibilité humaine pour un fichier de configuration généré automatiquement.
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();
}
}
La méthode récursive suivante utilise la réflexion pour trier la liste de jetons interne sur une instance JObject
existante au lieu de créer un nouveau graphe d'objet trié. Ce code s'appuie sur les détails de l'implémentation Json.NET interne et ne doit pas être utilisé en production.
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);
}
}
Si vous contrôlez (c'est-à-dire écrivez) la classe, placez les propriétés dans l'ordre alphabétique. Elles seront sérialisées dans l'ordre alphabétique lorsque JsonConvert.SerializeObject()
sera appelé.