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 préciser qu'un seul champ apparaît toujours en premier.
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 préciser qu'un seul champ apparaît toujours en premier.
Réponses:
La méthode prise en charge consiste à utiliser l' JsonProperty
attribut sur les propriétés de classe pour lesquelles vous souhaitez définir l'ordre. Lisez la documentation de la commande JsonPropertyAttribute pour plus d'informations.
Passez la valeur JsonProperty
an Order
et le sérialiseur s'occupera du reste.
[JsonProperty(Order = 1)]
Ceci est très similaire au
DataMember(Order = 1)
des System.Runtime.Serialization
jours.
Voici une note importante de @ kevin-babcock
... définir l'ordre sur 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 Order recevra un ordre de -1. Vous devez donc soit donner toutes les propriétés sérialisées et commander, soit définir votre premier élément sur -2
Order
propriété de JsonPropertyAttribute
peut être utilisée pour contrôler l'ordre dans lequel les champs sont sérialisés / désérialisés. Cependant, définir l'ordre sur 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 Order recevra un ordre de -1. Vous devez donc soit donner toutes les propriétés sérialisées et l'ordre, soit définir votre premier élément sur -2.
JavaScriptSerializer
.
Vous pouvez en fait contrôler l'ordre en implémentant IContractResolver
ou en remplaçant la méthode DefaultContractResolver
s CreateProperties
.
Voici un exemple de ma mise en œuvre simple 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 CreateProperties
méthode n'a jamais été appelée.
Après quelques débogages des Newtonsoft.Json
internes, j'ai trouvé 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 n'a pas fonctionné car elle ne gérait pas les objets dans les tableaux.
Sur la base de sa solution, c'est ce que j'ai proposé
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 quelque peu contrôler l'ordre des propriétés JSON en ordonnant les propriétés 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 classées telles qu'elles sont présentées dans le code, mais apparaîtront avant les propriétés de la classe de base.
Et pour tous ceux qui se demandent pourquoi vous souhaitez classer par ordre alphabétique les propriétés JSON, il est beaucoup plus facile de travailler avec des fichiers JSON bruts, en particulier pour les classes avec 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);
CreateProperties
n'est pas appelée lors de la sérialisation d'un dictionnaire. J'ai exploré le repo JSON.net pour savoir quelles machines sont réellement en train de parcourir les entrées du dictionnaire. Il ne se connecte à aucune override
personnalisation pour la commande. Il prend simplement les entrées telles quelles de l'énumérateur de l'objet. Il semble que je doive construire un SortedDictionary
ou SortedList
pour forcer JSON.net à le faire. Suggestion de fonctionnalité déposée: github.com/JamesNK/Newtonsoft.Json/issues/2270
Si vous ne voulez pas mettre un JsonProperty
Order
attribut sur chaque propriété de classe, il est très simple de créer votre propre ContractResolver ...
L'interface IContractResolver fournit un moyen de personnaliser la façon dont JsonSerializer sérialise et désérialise les objets .NET en JSON sans placer d'attributs sur vos classes.
Comme ça:
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;
}
}
Mettre en place:
var settings = new JsonSerializerSettings { ContractResolver = SortedPropertiesContractResolver.Instance };
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
La méthode récursive suivante utilise la réflexion pour trier la liste de jetons interne sur une JObject
instance existante plutôt que de créer un tout nouveau graphique d'objets triés. Ce code s'appuie sur les détails d'implémentation Json.NET internes 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);
}
}
En fait, comme mon Object était déjà un JObject, 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);
}
}
puis utilisez-le comme ceci:
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)]
parce que la classe elle-même est hors de ma 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 n'est peut-être pas à toute épreuve, car nulle part n'est défini qui 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();
}
}
Si vous souhaitez configurer globalement votre API avec des champs ordonnés, veuillez combiner la réponse de 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();
}
}
avec ma réponse ici:
METTRE À JOUR
Je viens de voir les votes négatifs. Veuillez consulter la réponse de «Steve» ci-dessous pour savoir comment procéder.
ORIGINAL
J'ai suivi l' JsonConvert.SerializeObject(key)
appel de méthode via la réflexion (où la clé était un IList) et j'ai trouvé que JsonSerializerInternalWriter.SerializeList était appelé. Il prend une liste et boucle via
for (int i = 0; i < values.Count; i++) { ...
où values est le paramètre IList introduit.
La réponse courte est ... Non, il n'y a pas de moyen intégré de définir l'ordre dans lequel les champs sont répertoriés dans la chaîne JSON.
Il n'y a pas d'ordre des champs au format JSON, donc définir un ordre n'a pas de sens.
{ id: 1, name: 'John' }
est équivalent à { name: 'John', id: 1 }
(les deux représentent une instance d'objet strictement équivalente)