¿Hay alguna manera de especificar el orden de los campos en un objeto JSON serializado usando JSON.NET ?
Sería suficiente especificar que un solo campo siempre aparece primero.
¿Hay alguna manera de especificar el orden de los campos en un objeto JSON serializado usando JSON.NET ?
Sería suficiente especificar que un solo campo siempre aparece primero.
Respuestas:
La forma admitida es utilizar el JsonProperty
atributo en las propiedades de clase para las que desea establecer el orden. Lea la documentación del pedido JsonPropertyAttribute para obtener más información.
Pasar el JsonProperty
un Order
valor y el serializador se hará cargo del resto.
[JsonProperty(Order = 1)]
Esto es muy similar al
DataMember(Order = 1)
de los System.Runtime.Serialization
dias.
Aquí hay una nota importante de @ kevin-babcock
... establecer el orden en 1 solo funcionará si establece un orden mayor que 1 en todas las demás propiedades. Por defecto, cualquier propiedad sin una configuración de Orden recibirá un orden de -1. Por lo tanto, debe proporcionar todas las propiedades y el orden serializados, o establecer su primer elemento en -2
Order
propiedad de JsonPropertyAttribute
se puede utilizar para controlar el orden en que los campos se serializan / deserializan. Sin embargo, establecer el orden en 1 solo funcionará si establece un orden mayor que 1 en todas las demás propiedades. Por defecto, cualquier propiedad sin una configuración de Orden recibirá un orden de -1. Por lo tanto, debe dar todas las propiedades y el orden serializados, o establecer su primer elemento en -2.
JavaScriptSerializer
.
En realidad se puede controlar el orden mediante la aplicación IContractResolver
o anulando la DefaultContractResolver
's CreateProperties
método.
Aquí hay un ejemplo de mi implementación simple IContractResolver
que ordena las propiedades alfabéticamente:
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();
}
}
Y luego establezca la configuración y serialice el objeto, y los campos JSON estarán en orden alfabético:
var settings = new JsonSerializerSettings()
{
ContractResolver = new OrderedContractResolver()
};
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
En mi caso, la respuesta de Mattias no funcionó. El CreateProperties
método nunca fue llamado.
Después de algunas depuraciones Newtonsoft.Json
internas, se me ocurrió otra solución.
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;
}
}
En mi caso, la solución de niaher no funcionó porque no manejaba objetos en matrices.
Basado en su solución, esto es lo que se me ocurrió
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;
}
}
}
Como señaló Charlie, puede controlar de alguna manera el orden de las propiedades JSON ordenando las propiedades en la clase misma. Desafortunadamente, este enfoque no funciona para propiedades heredadas de una clase base. Las propiedades de la clase base se ordenarán tal como se presentan en el código, pero aparecerán antes que las propiedades de la clase base.
Y para cualquiera que se pregunte por qué es posible que desee alfabetizar las propiedades JSON, es mucho más fácil trabajar con archivos JSON sin procesar, particularmente para clases con muchas propiedades, si están ordenadas.
Esto funcionará también para clases normales, diccionarios y ExpandoObject (objeto dinámico).
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
no se invoca durante la serialización de un diccionario. Exploré el repositorio de JSON.net para saber qué maquinaria está pasando por las entradas del diccionario. No se engancha en ninguna override
u otra personalización para ordenar. Solo toma las entradas como están del enumerador del objeto. Parece que tengo que construir SortedDictionary
o SortedList
forzar a JSON.net a hacer esto. Sugerencia de función presentada: github.com/JamesNK/Newtonsoft.Json/issues/2270
Si no desea poner un JsonProperty
Order
atributo en cada propiedad de clase, entonces es muy simple hacer su propio ContractResolver ...
La interfaz IContractResolver proporciona una manera de personalizar cómo el JsonSerializer serializa y deserializa los objetos .NET a JSON sin colocar atributos en sus clases.
Me gusta esto:
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;
}
}
Implementar:
var settings = new JsonSerializerSettings { ContractResolver = SortedPropertiesContractResolver.Instance };
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
El siguiente método recursivo utiliza la reflexión para ordenar la lista de tokens internos en una JObject
instancia existente en lugar de crear un nuevo gráfico de objetos ordenados. Este código se basa en los detalles internos de implementación de Json.NET y no debe usarse en producción.
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 realidad, dado que mi Object ya era un JObject, utilicé la siguiente solución:
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);
}
}
y luego úsalo así:
string serializedObj = JsonConvert.SerializeObject(new SortedJObject(dataObject));
Quiero serializar un objeto comblex y mantener el orden de las propiedades tal como se definieron en el código. No puedo simplemente agregar [JsonProperty(Order = 1)]
porque la clase en sí está fuera de mi alcance.
Esta solución también tiene en cuenta que las propiedades que se definen en una clase base deberían tener una prioridad más alta.
Esto puede no ser a prueba de balas, ya que no se define en ninguna parte que MetaDataAttribute
garantice el orden correcto, pero parece funcionar. Para mi caso de uso, esto está bien. ya que solo quiero mantener la legibilidad humana para un archivo de configuración generado automáticamente.
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 desea configurar globalmente su API con campos ordenados, combine la respuesta 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();
}
}
con mi respuesta aquí:
¿Cómo forzar la API web ASP.NET para que siempre devuelva JSON?
ACTUALIZAR
Acabo de ver los votos negativos. Consulte la respuesta de 'Steve' a continuación para saber cómo hacer esto.
ORIGINAL
Seguí la JsonConvert.SerializeObject(key)
llamada al método a través de la reflexión (donde la clave era una IList) y descubrí que se llama a JsonSerializerInternalWriter.SerializeList. Toma una lista y recorre a través de
for (int i = 0; i < values.Count; i++) { ...
donde valores es el parámetro IList introducido.
La respuesta corta es ... No, no hay una forma integrada de establecer el orden en que los campos se enumeran en la cadena JSON.
No hay un orden de campos en el formato JSON, por lo que definir un orden no tiene sentido.
{ id: 1, name: 'John' }
es equivalente a { name: 'John', id: 1 }
(ambos representan una instancia de objeto estrictamente equivalente)