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

Posted

技术标签:

【中文标题】使用 JSON.NET 的序列化字段的顺序【英文标题】:Order of serialized fields using JSON.NET 【发布时间】:2011-03-20 20:20:52 【问题描述】:

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

指定一个字段总是首先出现就足够了。

【问题讨论】:

我认为他可能有兴趣先显示 ID 字段(或类似字段),然后再显示所有其他字段。这对最终用户来说比在以 A..I 开头的字段后查找更友好 JSON 属性被定义为无序。我认为在序列化期间强制特定的 OUTPUT 顺序绝对没问题(也许是为了观察 JSON),但是在反序列化的任何特定顺序上创建 DEPENDENCY 将是一个糟糕的决定。 几个正当理由:(1) 伪造必须是 JSON 中的第一个属性的“$type”属性,(2) 尝试生成尽可能压缩的 JSON 另一个原因可能是 (3) 使用 JSON 语法的规范表示 - 必须保证相同的对象产生相同的 JSON 字符串。属性的确定顺序是这样做的必要先决条件。 Kevin,你能更新一下这个问题的接受答案吗? 【参考方案1】:

支持的方法是在要为其设置顺序的类属性上使用JsonProperty 属性。阅读JsonPropertyAttribute order documentation了解更多信息。

传递 JsonPropertyOrder 值,序列化程序将处理其余的工作。

 [JsonProperty(Order = 1)]

这个很像

 DataMember(Order = 1) 

System.Runtime.Serialization 天。

这是来自@kevin-babcock 的重要说明

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

【讨论】:

使用JsonPropertyAttributeOrder 属性可用于控制字段序列化/反序列化的顺序。但是,仅当您在所有其他属性上设置的顺序大于 1 时,将顺序设置为 1 才有效。默认情况下,任何没有 Order 设置的属性都将被赋予 -1 的顺序。因此,您必须要么提供所有序列化的属性和顺序,要么将您的第一项设置为 -2。 它适用于序列化,但在反序列化时不考虑顺序。根据文档, order 属性用于序列化和反序列化。有解决办法吗? javascriptSerializer 是否有类似的属性。 @cangosta 反序列化的顺序应该无关紧要......除了一些非常“奇怪”的预期情况。 阅读关于在反序列化中尊重 Order 的愿望的类似 github 问题讨论:github.com/JamesNK/Newtonsoft.Json/issues/758 基本上没有这个机会。【参考方案2】:

您实际上可以通过实现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);

【讨论】:

这很有帮助(+1),但有一个警告:字典的序列化似乎不使用此 CreateProperties 自定义。他们序列化很好,但最终没有排序。我假设有一种不同的方法可以自定义字典的序列化,但我还没有找到。 这是一个很好的解决方案。非常适合我,尤其是在并排放置 2 个 JSON 对象并排列属性时。【参考方案3】:

就我而言,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;
    

【讨论】:

这是我们在使用 dicts 时需要解决的问题。 这增加了额外的反序列化和序列化的开销。我添加了一个适用于普通类、字典和 ExpandoObject(动态对象)的解决方案 总体上有用,但缺少对数组的处理(对数组内对象内的对象键进行排序)。相对容易添加。【参考方案4】:

在我的情况下,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;
        
    

【讨论】:

这增加了额外的反序列化和序列化的开销。【参考方案5】:

这也适用于普通类、字典和 ExpandoObject(动态对象)。

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 在字典序列化期间不会被调用。我探索了 JSON.net 存储库,以了解哪些机器实际上正在浏览字典条目。它不会挂接到任何override 或其他定制的订购。它只是从对象的枚举器中获取条目。看来我必须构造一个 SortedDictionarySortedList 来强制 JSON.net 执行此操作。功能建议提交:github.com/JamesNK/Newtonsoft.Json/issues/2270【参考方案6】:

如果您只想将单个属性拉到前面而不考虑可能不直观的数字系统,只需使用int.MinValue

[JsonProperty(Order = int.MinValue)]

【讨论】:

【参考方案7】:

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

对于任何想知道为什么要按字母顺序排列 JSON 属性的人,使用原始 JSON 文件要容易得多,特别是对于具有大量属性的类(如果它们是有序的)。

【讨论】:

【参考方案8】:

如果您不想在每个类属性上放置 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);

【讨论】:

【参考方案9】:

其实我的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));

【讨论】:

【参考方案10】:

以下递归方法使用反射对现有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);
    

【讨论】:

【参考方案11】:

如果您控制(即编写)该类,请将属性按字母顺序排列,当调用JsonConvert.SerializeObject() 时,它们将按字母顺序序列化。

【讨论】:

【参考方案12】:

我想序列化一个complex 对象并保持属性的顺序,因为它们在代码中定义。我不能只添加[JsonProperty(Order = 1)],因为该类本身超出了我的范围。

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

这可能不是万无一失的,因为没有任何地方定义 MetaDataAttribute 确保正确的顺序,但它似乎有效。对于我的用例,这没关系。因为我只想为自动生成的配置文件保持人类可读性。

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


【讨论】:

【参考方案13】:

如果您想使用有序字段全局配置您的 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();
    

这里有我的回答:

How to force ASP.NET Web API to always return JSON?

【讨论】:

【参考方案14】:

更新

我刚刚看到了反对票。请参阅下面“史蒂夫”的答案,了解如何执行此操作。

原创

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

循环

for (int i = 0; i &lt; values.Count; i++) ...

其中 values 是引入的 IList 参数。

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

【讨论】:

简短回答,但可能已过时。查看史蒂夫的回答(由 James Newton-king 提供支持)【参考方案15】:

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

id: 1, name: 'John' 等价于 name: 'John', id: 1 (都表示严格等价的对象实例)

【讨论】:

@Darin - 但序列化中有一个顺序。 " id: 1, name: 'John' " 和 " name: 'John', id: 1 " 作为 strings 是不同的,这是我在这里关心的。当然,反序列化后的对象是等价的。 @Darin - 不,在这种情况下不是。我正在序列化某些内容,然后将其作为字符串传递给仅处理字符串(不支持 JSON)的服务,出于各种原因,一个字段首先出现在字符串中会很方便。跨度> 它也有利于测试,能够只查看字符串而不必反序列化。 稳定的序列化顺序对于缓存验证也很方便。对字符串进行校验和是微不足道的——对于完整的对象图来说不是这样。 序列化顺序在进行单元测试时也很方便,因此即使 json 属性的顺序不同,您也可以轻松地说预期响应字符串与实际响应字符串相等。

以上是关于使用 JSON.NET 的序列化字段的顺序的主要内容,如果未能解决你的问题,请参考以下文章

使用 JSON.NET 反序列化 DateTime 时如何保留时区? [复制]

Json.net 忽略实体某些属性的序列化

JSON.net 反序列化对象嵌套数据

Json.NET 是不是缓存类型的序列化信息?

JSON.net:如何在不使用默认构造函数的情况下反序列化?

Json.Net 两次没有以相同的方式序列化小数