C'è un modo per specificare l'ordine dei campi in un oggetto JSON serializzato usando JSON.NET ?
Sarebbe sufficiente specificare che un singolo campo appare sempre per primo.
C'è un modo per specificare l'ordine dei campi in un oggetto JSON serializzato usando JSON.NET ?
Sarebbe sufficiente specificare che un singolo campo appare sempre per primo.
Risposte:
Il modo supportato è utilizzare l' JsonProperty
attributo sulle proprietà della classe per cui si desidera impostare l'ordine. Leggi la documentazione dell'ordine di JsonPropertyAttribute per ulteriori informazioni.
Passa il valore JsonProperty
an Order
e il serializzatore si occuperà del resto.
[JsonProperty(Order = 1)]
Questo è molto simile al
DataMember(Order = 1)
dei System.Runtime.Serialization
giorni.
Ecco una nota importante di @ kevin-babcock
... impostando l'ordine su 1 funzionerà solo se imposti un ordine maggiore di 1 su tutte le altre proprietà. Per impostazione predefinita, a qualsiasi proprietà senza impostazione Ordine verrà assegnato un ordine di -1. Quindi devi dare tutte le proprietà e l'ordine serializzati oppure impostare il tuo primo oggetto su -2
Order
proprietà di JsonPropertyAttribute
può essere utilizzato per controllare l'ordine in cui i campi sono serializzati / deserializzati. Tuttavia, l'impostazione dell'ordine su 1 funzionerà solo se si imposta un ordine maggiore di 1 su tutte le altre proprietà. Per impostazione predefinita, a qualsiasi proprietà senza impostazione Ordine verrà assegnato un ordine di -1. Quindi devi dare tutte le proprietà e l'ordine serializzati oppure impostare il tuo primo oggetto su -2.
JavaScriptSerializer
.
Si può effettivamente controllare l'ordine mediante l'attuazione IContractResolver
o l'override del DefaultContractResolver
's CreateProperties
metodo.
Ecco un esempio della mia semplice implementazione di IContractResolver
cui ordina le proprietà in ordine alfabetico:
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();
}
}
Quindi imposta le impostazioni e serializza l'oggetto, ei campi JSON saranno in ordine alfabetico:
var settings = new JsonSerializerSettings()
{
ContractResolver = new OrderedContractResolver()
};
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
Nel mio caso la risposta di Mattias non ha funzionato. Il CreateProperties
metodo non è mai stato chiamato.
Dopo un po 'di debug degli Newtonsoft.Json
interni, ho trovato un'altra soluzione.
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;
}
}
Nel mio caso la soluzione di niaher non ha funzionato perché non ha gestito gli oggetti negli array.
Sulla base della sua soluzione, questo è quello che mi è venuto in mente
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;
}
}
}
Come notato da Charlie, puoi in qualche modo controllare l'ordinamento delle proprietà JSON ordinando le proprietà nella classe stessa. Sfortunatamente, questo approccio non funziona per le proprietà ereditate da una classe base. Le proprietà della classe base saranno ordinate come sono disposte nel codice, ma appariranno prima delle proprietà della classe base.
E per chiunque si chieda perché potresti voler alfabetizzare le proprietà JSON, è molto più semplice lavorare con file JSON non elaborati, in particolare per le classi con molte proprietà, se sono ordinate.
Funzionerà anche per normali classi, dizionari e ExpandoObject (oggetto dinamico).
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
non viene richiamato durante la serializzazione di un dizionario. Ho esplorato il repository JSON.net per quale macchinario sta effettivamente analizzando le voci del dizionario. Non si aggancia a nessuna override
o altra personalizzazione per l'ordinazione. Prende le voci così come sono dall'enumeratore dell'oggetto. Sembra che debba costruire un SortedDictionary
o SortedList
forzare JSON.net a farlo. Suggerimento di funzionalità archiviato: github.com/JamesNK/Newtonsoft.Json/issues/2270
Se non vuoi mettere un JsonProperty
Order
attributo su ogni proprietà della classe, è molto semplice creare il tuo ContractResolver ...
L'interfaccia IContractResolver fornisce un modo per personalizzare il modo in cui JsonSerializer serializza e deserializza gli oggetti .NET su JSON senza posizionare gli attributi nelle classi.
Come questo:
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;
}
}
Strumento:
var settings = new JsonSerializerSettings { ContractResolver = SortedPropertiesContractResolver.Instance };
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
Il seguente metodo ricorsivo utilizza la riflessione per ordinare l'elenco di token interno su JObject
un'istanza esistente anziché creare un nuovo grafico di oggetti ordinati. Questo codice si basa sui dettagli interni dell'implementazione di Json.NET e non deve essere utilizzato in produzione.
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);
}
}
In realtà, poiché il mio oggetto era già un JObject, ho usato la seguente soluzione:
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);
}
}
e poi usalo in questo modo:
string serializedObj = JsonConvert.SerializeObject(new SortedJObject(dataObject));
Voglio serializzare un oggetto comblex e mantenere l'ordine delle proprietà come sono state definite nel codice. Non posso semplicemente aggiungere [JsonProperty(Order = 1)]
perché la classe stessa è fuori dal mio ambito.
Questa soluzione tiene anche conto del fatto che le proprietà definite in una classe base dovrebbero avere una priorità più alta.
Questo potrebbe non essere a prova di proiettile, poiché da nessuna parte è definito che MetaDataAttribute
garantisce l'ordine corretto, ma sembra funzionare. Per il mio caso d'uso questo è ok. poiché voglio solo mantenere la leggibilità umana per un file di configurazione generato automaticamente.
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();
}
}
Se desideri configurare globalmente la tua API con i campi ordinati, combina la risposta di 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();
}
}
con la mia risposta qui:
AGGIORNARE
Ho appena visto i voti negativi. Si prega di vedere la risposta da 'Steve' di seguito per come fare.
ORIGINALE
Ho seguito la JsonConvert.SerializeObject(key)
chiamata del metodo tramite reflection (dove key era un IList) e ho scoperto che viene chiamato JsonSerializerInternalWriter.SerializeList. Prende un elenco e scorre attraverso
for (int i = 0; i < values.Count; i++) { ...
dove valori è il parametro IList introdotto.
La risposta breve è ... No, non esiste un modo integrato per impostare l'ordine in cui i campi sono elencati nella stringa JSON.
Non esiste un ordine di campi nel formato JSON, quindi la definizione di un ordine non ha senso.
{ id: 1, name: 'John' }
è equivalente a { name: 'John', id: 1 }
(entrambi rappresentano un'istanza di oggetto strettamente equivalente)