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.
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.
Respostas:
A maneira suportada é usar o JsonProperty
atributo 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 JsonProperty
um Order
valor e o serializador vai cuidar do resto.
[JsonProperty(Order = 1)]
Isso é muito semelhante ao
DataMember(Order = 1)
dos System.Runtime.Serialization
dias.
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
Order
propriedade de JsonPropertyAttribute
pode 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.
JavaScriptSerializer
.
Você pode realmente controlar a ordem através da implementação IContractResolver
ou substituindo o DefaultContractResolver
's CreateProperties
método.
Aqui está um exemplo da minha implementação simples, IContractResolver
que 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);
No meu caso, a resposta de Mattias não funcionou. O CreateProperties
método nunca foi chamado.
Após alguma depuração de Newtonsoft.Json
componentes 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;
}
}
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;
}
}
}
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.
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);
CreateProperties
nã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 override
ou outra personalização para fazer pedidos. Ele apenas aceita as entradas do enumerador do objeto. Parece que tenho que construir um SortedDictionary
ou SortedList
forçar o JSON.net a fazer isso. Sugestão de recurso arquivada: github.com/JamesNK/Newtonsoft.Json/issues/2270
Se você não deseja colocar um JsonProperty
Order
atributo 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);
O método recursivo a seguir usa reflexão para classificar a lista de tokens internos em uma JObject
instâ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);
}
}
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));
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 MetaDataAttribute
garante 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();
}
}
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:
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.
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)