c# - Orden de campos serializados usando JSON.NET
(10)
¿Hay alguna forma de especificar el orden de los campos en un objeto JSON serializado utilizando JSON.NET ?
Sería suficiente especificar que un solo campo siempre aparece primero.
Como señaló Charlie, puede controlar el orden de las propiedades JSON ordenando las propiedades de la clase. Desafortunadamente, este enfoque no funciona para las 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 de las propiedades de la clase base.
Y para cualquiera que se pregunte por qué es posible que desee alfabetizar propiedades JSON, es mucho más fácil trabajar con archivos JSON sin formato, especialmente para clases con muchas propiedades, si se solicitan.
El siguiente método recursivo utiliza la reflexión para ordenar la lista de tokens internos en una instancia de JObject
existente en lugar de crear un nuevo gráfico de objetos ordenados. Este código se basa en los detalles de la implementación interna de Json.NET y no debe utilizarse 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 mi caso, la respuesta de Mattias no funcionó. El método CreateProperties
nunca se llamó.
Después de algunas depuraciones de los Newtonsoft.Json
internos de Newtonsoft.Json
, 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;
}
}
}
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 usarlo así:
string serializedObj = JsonConvert.SerializeObject(new SortedJObject(dataObject));
En realidad, puede controlar el pedido implementando IContractResolver
o reemplazando el método CreateProperties
DefaultContractResolver
.
Aquí hay un ejemplo de mi implementación simple de 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 configure 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);
La forma admitida es usar el atributo JsonProperty
en las propiedades de clase para las que desea establecer el orden. Lea la documentación de la orden JsonPropertyAttribute para obtener más información .
Pase a JsonProperty
un valor de Order
y el serializador se encargará del resto.
[JsonProperty(Order = 1)]
Esto es muy similar al
DataMember(Order = 1)
de los días System.Runtime.Serialization
.
No hay ningún orden de campos en el formato JSON, por lo que definir un pedido no tiene sentido.
{ id: 1, name: ''John'' }
es equivalente a { name: ''John'', id: 1 }
(ambos representan una instancia de objeto estrictamente equivalente)
Si controla (es decir, escribe) la clase, coloque las propiedades en orden alfabético y se serializarán en orden alfabético cuando se JsonConvert.SerializeObject()
.
Seguí la llamada al método JsonConvert.SerializeObject(key)
a través de la reflexión (donde la clave era un IList) y encontré que se llama a JsonSerializerInternalWriter.SerializeList. Toma una lista y pasa 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 para establecer el orden en que se enumeran los campos en la cadena JSON.