Orden de campos serializados usando JSON.NET

138

¿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.

Kevin Montrose
fuente
77
Creo que probablemente esté interesado en mostrar primero el campo ID (o similar) y luego todos los demás campos. esto es más amigable para los usuarios finales que buscarlo después de los campos que comienzan con A..I
Michael Bahig
3
Las propiedades JSON se definen como desordenadas. Creo que está absolutamente bien forzar un pedido de SALIDA en particular durante la serialización (tal vez por mirar el JSON tal vez), pero sería una mala decisión crear una DEPENDENCIA en cualquier orden particular de deserialización.
DaBlick
55
Un par de razones válidas: (1) falsificar una propiedad "$ type" que debe ser la primera propiedad en el JSON, (2) tratar de generar un JSON que se comprima tanto como sea posible
Stephen Chung
44
Otra razón podría ser (3) una representación canónica que utiliza la sintaxis JSON: se debe garantizar que el mismo objeto produzca la misma cadena JSON. Un orden determinista de los atributos es un requisito previo necesario para esto.
MarkusSchaber
2
Kevin, ¿puedes actualizar la respuesta aceptada a esta pregunta?
Millie Smith

Respuestas:

256

La forma admitida es utilizar el JsonPropertyatributo 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 JsonPropertyun Ordervalor y el serializador se hará cargo del resto.

 [JsonProperty(Order = 1)]

Esto es muy similar al

 DataMember(Order = 1) 

de los System.Runtime.Serializationdias.

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

Steve
fuente
97
El uso de la Orderpropiedad de JsonPropertyAttributese 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.
Kevin Babcock
1
Funciona para la serialización, pero el orden no se está considerando en la deserialización. Según la documentación, el atributo de pedido se utiliza tanto para la serialización como para la deserialización. ¿Hay alguna solución?
cangosta
1
¿Existe una propiedad similar para el JavaScriptSerializer.
Shimmy Weitzhandler
44
@cangosta El orden de deserialización no debería importar ... excepto en algunos casos de expectativas muy "extrañas".
usuario2864740
1
Lea la discusión de temas similares de Github sobre el deseo de que se respete el Orden en la deserialización: github.com/JamesNK/Newtonsoft.Json/issues/758 Básicamente no hay posibilidad de que esto ocurra .
Tyeth
126

En realidad se puede controlar el orden mediante la aplicación IContractResolvero anulando la DefaultContractResolver's CreatePropertiesmétodo.

Aquí hay un ejemplo de mi implementación simple IContractResolverque 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);
Mattias Nordberg
fuente
11
Esto es bastante útil (+1) pero una advertencia: parece que la serialización de diccionarios no usa esta personalización CreateProperties. Serianizan bien pero no terminan ordenados. Supongo que hay una forma diferente de personalizar la serialización de diccionarios, pero no la he encontrado.
solublefish
Perfecto. Hace justo lo que quería. Gracias.
Wade Hatler
Esta es una gran solución. Funcionó perfectamente para mí, especialmente al poner 2 objetos JSON uno al lado del otro y hacer que las propiedades se alineen.
Vince
16

En mi caso, la respuesta de Mattias no funcionó. El CreatePropertiesmétodo nunca fue llamado.

Después de algunas depuraciones Newtonsoft.Jsoninternas, 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;
    }
}
niaher
fuente
2
Esta fue la solución necesaria para nosotros cuando usamos dicts.
noocyte
Esto agrega sobrecarga de deserialización y serialización adicionales. He agregado una solución que también funcionará para clases normales, diccionarios y ExpandoObject (objeto dinámico)
Jay Shah
11

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;
        }
    }
}
Tuan-Tu Tran
fuente
Esto agrega sobrecarga de deserialización y serialización adicionales.
Jay Shah
Excelente solucion. Gracias.
Maya
3

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.

Jack Bond
fuente
2

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);
Jay Shah
fuente
¿No era este el comportamiento de pedido predeterminado durante la serialización?
Mr5
1
Para ahorrarle a alguien más unos minutos desperdiciados, tenga en cuenta que esta respuesta no funciona para los diccionarios a pesar del reclamo. CreatePropertiesno 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 overrideu otra personalización para ordenar. Solo toma las entradas como están del enumerador del objeto. Parece que tengo que construir SortedDictionaryo SortedListforzar a JSON.net a hacer esto. Sugerencia de función presentada: github.com/JamesNK/Newtonsoft.Json/issues/2270
William
2

Si no desea poner un JsonProperty Orderatributo 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);
CrazyTim
fuente
0

El siguiente método recursivo utiliza la reflexión para ordenar la lista de tokens internos en una JObjectinstancia 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);
    }
}
Nathan Baulch
fuente
0

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));
Danny R
fuente
0

Si controla (es decir, escribe) la clase, coloque las propiedades en orden alfabético y se serializarán en orden alfabético cuando JsonConvert.SerializeObject()se llame.

Charlie
fuente
0

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 MetaDataAttributegarantice 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();
    }
}

Jürgen Steinblock
fuente
-1

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?

Carlo Saccone
fuente
-5

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.

DougJones
fuente
18
Respuesta corta, pero posiblemente anticuada. Mira la respuesta de Steve (apoyada por James Newton-king)
Brad Bruce
-6

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)

Darin Dimitrov
fuente
12
@Darin, pero hay un orden en la serialización. "{id: 1, nombre: 'John'}" y "{nombre: 'John', id: 1}" son diferentes como cadenas , que es lo que me importa aquí. Por supuesto, los objetos son equivalentes cuando se deserializan.
Kevin Montrose
1
@Darin: no, no en este caso. Estoy serializando algo y luego pasándolo como una cadena a un servicio que solo se ocupa de cadenas (no es consciente de JSON), y sería conveniente por una variedad de razones para que un campo aparezca primero en la cadena.
Kevin Montrose
1
también es bueno para las pruebas, ya que solo puede mirar las cadenas en lugar de tener que deserializarlas.
Steve
9
Un orden de serialización estable también es útil para la validación de caché. Es trivial tomar una suma de verificación de una cadena, no es cierto para un gráfico de objeto completo.
solublefish
1
El orden de serialización también es útil cuando se realizan pruebas unitarias para que pueda decir fácilmente que las cadenas de respuesta esperadas frente a las reales son iguales incluso cuando el orden de las propiedades json es diferente.
anon