Ordem dos campos serializados usando JSON.NET

138

Existe uma maneira de especificar a ordem dos campos em um objeto JSON serializado usando JSON.NET ?

Seria suficiente especificar que um único campo sempre aparecesse primeiro.

Kevin Montrose
fonte
7
Eu acho que ele provavelmente está interessado em mostrar o campo ID (ou similar) primeiro e depois todos os outros campos. este é mais amigável para os usuários finais do que olhar para ele depois de campos que começam com A..I
Michael Bahig
3
As propriedades JSON são definidas para serem desordenadas. Eu acho absolutamente bom forçar uma ordem de SAÍDA específica durante a serialização (talvez para dar uma olhada no JSON), mas seria uma má decisão criar uma DEPENDÊNCIA em qualquer ordem específica de desserialização.
DaBlick
5
Um par de razões válidas: (1) falsificando uma propriedade "$ type", que deve ser a primeira propriedade no JSON, (2) tentando gerar JSON que comprima o máximo possível
Stephen Chung
4
Outro motivo pode ser (3) uma representação canônica que usa a sintaxe JSON - o mesmo objeto deve ser garantido para produzir a mesma sequência JSON. Uma ordem determinística dos atributos é um pré-requisito necessário para isso.
MarkusSchaber
2
Kevin, você pode atualizar a resposta aceita nesta pergunta?
Millie Smith

Respostas:

256

A maneira suportada é usar o JsonPropertyatributo nas propriedades da classe para as quais você deseja definir a ordem. Leia a documentação do pedido JsonPropertyAttribute para obter mais informações.

Passe a JsonPropertyum Ordervalor e o serializador vai cuidar do resto.

 [JsonProperty(Order = 1)]

Isso é muito semelhante ao

 DataMember(Order = 1) 

dos System.Runtime.Serializationdias.

Aqui está uma nota importante de @ kevin-babcock

... definir o pedido como 1 só funcionará se você definir um pedido maior que 1 em todas as outras propriedades. Por padrão, qualquer propriedade sem uma configuração de Pedido receberá uma ordem de -1. Portanto, você deve fornecer todas as propriedades serializadas e ordem ou definir seu primeiro item como -2

Steve
fonte
97
O uso da Orderpropriedade de JsonPropertyAttributepode ser usado para controlar a ordem na qual os campos são serializados / desserializados. No entanto, definir o pedido como 1 só funcionará se você definir um pedido maior que 1 em todas as outras propriedades. Por padrão, qualquer propriedade sem uma configuração de Pedido receberá uma ordem de -1. Portanto, você deve fornecer todas as propriedades e ordem serializadas ou definir seu primeiro item como -2.
Kevin Babcock
1
Funciona para serialização, mas o pedido não está sendo considerado na desserialização. De acordo com a documentação, o atributo order é usado para serialização e desserialização. Existe uma solução alternativa?
Cangosta
1
Existe uma propriedade semelhante para o arquivo JavaScriptSerializer.
Shimmy Weitzhandler
4
@cangosta A ordem de desserialização não deve importar .. exceto em alguns casos de expectativa muito "estranhos".
user2864740
1
Leia a discussão semelhante sobre questões do github em torno do desejo de que a Ordem seja respeitada na desserialização: github.com/JamesNK/Newtonsoft.Json/issues/758 Basicamente, não há chance disso.
Tyeth
126

Você pode realmente controlar a ordem através da implementação IContractResolverou substituindo o DefaultContractResolver's CreatePropertiesmétodo.

Aqui está um exemplo da minha implementação simples, IContractResolverque ordena as propriedades em ordem alfabética:

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

E defina as configurações e serialize o objeto, e os campos JSON estarão em ordem alfabética:

var settings = new JsonSerializerSettings()
{
    ContractResolver = new OrderedContractResolver()
};

var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
Mattias Nordberg
fonte
11
Isso é bastante útil (+1), mas uma ressalva: parece que a serialização de dicionários não usa essa personalização do CreateProperties. Eles serializam bem, mas não acabam classificados. Suponho que haja uma maneira diferente de personalizar a serialização de dicionários, mas não a encontrei.
Solublefish
Perfeito. Faz exatamente o que eu queria. Obrigado.
Wade Hatler,
Esta é uma otima soluçao. Funcionou perfeitamente para mim, especialmente ao colocar 2 objetos JSON lado a lado e ter as propriedades alinhadas.
Vince
16

No meu caso, a resposta de Mattias não funcionou. O CreatePropertiesmétodo nunca foi chamado.

Após alguma depuração de Newtonsoft.Jsoncomponentes internos, criei outra solução.

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
fonte
2
Esta foi a correção necessária para nós quando usamos dictos.
noocyte
Isso adiciona uma sobrecarga de desserialização e serialização adicionais. Eu adicionei uma solução which'll trabalho para aulas normais, dicionários e ExpandoObject (objeto dinâmico) bem
Jay Shah
11

No meu caso, a solução de niaher não funcionou porque não manipulava objetos em matrizes.

Com base em sua solução, é isso que eu criei

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
fonte
Isso adiciona uma sobrecarga de desserialização e serialização adicionais.
Jay Shah
Excelente solução. Obrigado.
Maia
3

Como Charlie observou, você pode controlar um pouco a ordem das propriedades JSON ordenando as propriedades na própria classe. Infelizmente, essa abordagem não funciona para propriedades herdadas de uma classe base. As propriedades da classe base serão ordenadas conforme dispostas no código, mas aparecerão antes das propriedades da classe base.

E para quem se pergunta por que você deseja alfabetizar as propriedades JSON, é muito mais fácil trabalhar com arquivos JSON brutos, principalmente para classes com muitas propriedades, se elas forem solicitadas.

Jack Bond
fonte
2

Isso funcionará para classes normais, dicionários e ExpandoObject (objeto dinâmico) também.

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
fonte
Não era esse o comportamento de pedido padrão durante a serialização?
Mr5
1
Para economizar a alguém alguns minutos desperdiçados, observe que essa resposta não funciona para dicionários, apesar da reivindicação. CreatePropertiesnão é chamado durante a serialização de um dicionário. Eu explorei o repositório JSON.net para saber quais máquinas estão realmente arrastando as entradas do dicionário. Ele não se encaixa em nenhuma overrideou outra personalização para fazer pedidos. Ele apenas aceita as entradas do enumerador do objeto. Parece que tenho que construir um SortedDictionaryou SortedListforçar o JSON.net a fazer isso. Sugestão de recurso arquivada: github.com/JamesNK/Newtonsoft.Json/issues/2270
William
2

Se você não deseja colocar um JsonProperty Orderatributo em todas as propriedades de classe, é muito simples criar seu próprio ContractResolver ...

A interface IContractResolver fornece uma maneira de personalizar como o JsonSerializer serializa e desserializa objetos .NET para JSON sem colocar atributos em suas classes.

Como isso:

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;
    }
}

Implemento:

var settings = new JsonSerializerSettings { ContractResolver = SortedPropertiesContractResolver.Instance };
var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
CrazyTim
fonte
0

O método recursivo a seguir usa reflexão para classificar a lista de tokens internos em uma JObjectinstância existente, em vez de criar um novo gráfico de objeto classificado. Esse código depende de detalhes internos da implementação do Json.NET e não deve ser usado na produção.

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
fonte
0

Na verdade, como meu Object já era um JObject, usei a seguinte solução:

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 use-o assim:

string serializedObj = JsonConvert.SerializeObject(new SortedJObject(dataObject));
Danny R
fonte
0

Se você controlar (ou seja, escrever) a classe, coloque as propriedades em ordem alfabética e elas serão serializadas em ordem alfabética quando JsonConvert.SerializeObject()for chamada.

Charlie
fonte
0

Eu quero serializar um objeto comblex e manter a ordem das propriedades conforme definidas no código. Não posso simplesmente adicionar [JsonProperty(Order = 1)]porque a própria classe está fora do meu escopo.

Essa solução também leva em consideração que as propriedades definidas em uma classe base devem ter uma prioridade mais alta.

Isso pode não ser à prova de balas, já que em nenhum lugar é definido que o MetaDataAttributegarante a ordem correta, mas parece funcionar. Para o meu caso de uso, tudo bem. desde que eu só quero manter a legibilidade humana para um arquivo de configuração gerado 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();
    }
}

Jürgen Steinblock
fonte
-1

Se você deseja configurar globalmente sua API com campos ordenados, combine a resposta 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();
    }
}

com a minha resposta aqui:

Como forçar a API da Web do ASP.NET a sempre retornar JSON?

Carlo Saccone
fonte
-5

ATUALIZAR

Acabei de ver os votos negativos. Consulte a resposta de 'Steve' abaixo para saber como fazer isso.

ORIGINAL

Eu segui a JsonConvert.SerializeObject(key)chamada do método via reflexão (onde key era uma IList) e descobri que JsonSerializerInternalWriter.SerializeList é chamado. Leva uma lista e percorre através de

for (int i = 0; i < values.Count; i++) { ...

onde valores é o parâmetro IList trazido.

A resposta curta é ... Não, não existe uma maneira integrada de definir a ordem em que os campos são listados na string JSON.

DougJones
fonte
18
Resposta curta, mas possivelmente desatualizada. Confira a resposta de Steve (apoiado por James Newton-rei)
Brad Bruce
-6

Não há ordem de campos no formato JSON, portanto, definir uma ordem não faz sentido.

{ id: 1, name: 'John' }é equivalente a { name: 'John', id: 1 }(ambos representam uma instância de objeto estritamente equivalente)

Darin Dimitrov
fonte
12
@ Darin - mas há uma ordem na serialização. "{id: 1, name: 'John'}" e "{name: 'John', id: 1}" são diferentes como strings , e é com isso que eu me importo aqui. Obviamente, os objetos são equivalentes quando desserializados.
Kevin Montrose
1
@ Darin - não, não neste caso. Estou serializando algo e, em seguida, transmitindo-o como uma string para um serviço que lida apenas com strings (sem o conhecimento do JSON), e seria conveniente, por vários motivos, que um campo aparecesse primeiro na string.
Kevin Montrose
1
é bom para testar também, ser capaz de olhar apenas para as strings em vez de precisar desserializar.
26412 Steve Steve
9
Um pedido de serialização estável também é útil para validação de cache. É trivial obter uma soma de verificação de uma string - não é verdade para um gráfico de objeto completo.
Solublefish
1
A ordem de serialização também é útil ao fazer testes de unidade, para que você possa dizer com facilidade que as seqüências de resposta esperadas versus as reais são iguais, mesmo quando a ordem das propriedades json é diferente.
Anon