使用 JSON.NET 的序列化字段的顺序

有没有使用 JSON.NET指定序列化 JSON 对象中字段顺序的方法?

只要指定单个字段总是先出现就足够了。

98826 次浏览

JSON 格式中没有字段的顺序,因此定义顺序是没有意义的。

{ id: 1, name: 'John' }等价于 { name: 'John', id: 1 }(两者都表示一个严格等价的对象实例)

更新

我刚刚看到了反对票,请看下面“史蒂夫”的回答。

原创的

我通过反射跟踪 JsonConvert.SerializeObject(key)方法调用(其中 key 是 IList) ,发现了 JsonSerializerInternalWriter。调用 SerializeList。它接受一个列表并循环通过

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

其中值是引入的 IList 参数。

简短的回答是... ... 不,没有内置的方式来设置 JSON 字符串中列出的字段的顺序。

您实际上可以通过实现 IContractResolver或重写 DefaultContractResolverCreateProperties方法来控制订单。

下面是我的简单 IContractResolver实现的一个例子,它按字母顺序排列属性:

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

然后设置设置并序列化对象,JSON 字段就会变成字母顺序:

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


var json = JsonConvert.SerializeObject(obj, Formatting.Indented, settings);

下面的递归方法使用反射对现有 JObject实例上的内部令牌列表进行排序,而不是创建全新的排序对象图。此代码依赖于内部 Json.NET 实现细节,不应在生产中使用。

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

受支持的方法是在要为其设置顺序的类属性上使用 JsonProperty属性。阅读 属性订单文档了解更多信息。

JsonProperty传递一个 Order值,序列化程序将处理其余的事情。

 [JsonProperty(Order = 1)]

这非常类似于

 DataMember(Order = 1)

System.Runtime.Serialization天。

这里有一个来自“ Kevin-Babcock”的重要提示

... 只有在所有其他属性上设置大于1的顺序时,才能将顺序设置为1。默认情况下,没有 Order 设置的任何属性将被赋予 -1的顺序。因此,您必须要么给出所有序列化的属性和顺序,要么将第一个项设置为 -2

在我的例子中,Mattias 的答案不起作用,从来没有调用 CreateProperties方法。

在对 Newtonsoft.Json内部进行了一些调试之后,我想出了另一个解决方案。

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

实际上,因为我的 Object 已经是一个 JObject,所以我使用了以下解决方案:

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

然后像这样使用它:

string serializedObj = JsonConvert.SerializeObject(new SortedJObject(dataObject));

在我的例子中,Niaher 的解决方案不起作用,因为它不能处理数组中的对象。

根据他的解决方案,这是我想出来的

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

如果你控制(比如写)这个类,把这些属性放在字母顺序中,当调用 JsonConvert.SerializeObject()时,它们会以字母顺序序列化。

正如 Charlie 指出的,通过对类本身中的属性进行排序,您可以在某种程度上控制 JSON 属性的排序。遗憾的是,这种方法不适用于从基类继承的属性。基类属性将按照它们在代码中的布局顺序排列,但是将出现在基类属性之前。

对于那些想知道为什么要对 JSON 属性进行字母排序的人来说,使用原始 JSON 文件要容易得多,特别是对于具有许多属性(如果它们是有序的)的类来说。

如果您想在全球范围内使用有序字段配置您的 API,请将 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();
}
}

我的回答是:

如何强制 ASP.NET Web API 始终返回 JSON?

我想序列化一个复合对象,并保持属性的顺序,就像它们在代码中定义的那样。我不能仅仅添加 [JsonProperty(Order = 1)],因为类本身超出了我的作用域。

此解决方案还考虑到在基类中定义的属性应该具有更高的优先级。

这可能不是防弹的,因为没有任何地方被定义为 MetaDataAttribute确保正确的顺序,但它似乎工作。对于我的用例来说,这是 OK 的。因为我只想为自动生成的配置文件保持人类可读性。

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

这同样适用于普通类、字典和 ExpendoObject (动态对象)。

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

如果您不想在每个类属性上放置一个 JsonProperty Order属性,那么制作您自己的 ContractResolver 非常简单..。

IContractResolver 接口提供了一种自定义 JsonSerializer 序列化和反序列化方式的方法。NET 对象转换为 JSON,而不在类上放置属性。

像这样:

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

实施:

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

如果你只是想把一个单一的属性拉到前面而不考虑也许不直观的数字系统,只是使用 int.MinValue

[JsonProperty(Order = int.MinValue)]