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


137

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

只需指定一个字段始终首先出现就足够了。


7
我认为他可能有兴趣先显示ID字段(或类似字段),然后显示所有其他字段。这是为最终用户比后场寻找它与A..I开始友好
迈克尔Bahig

3
JSON属性定义为无序的。我认为在序列化过程中强制执行特定的OUTPUT顺序(可能是为了盯着JSON)绝对好,但是在反序列化的任何特定顺序上创建DEPENDENCY都是一个错误的决定。
DaBlick 2015年

5
有两个有效的原因:(1)伪造“ $ type”属性,该属性必须是JSON中的第一个属性,(2)尝试生成尽可能压缩的JSON
Stephen Chung

4
另一个原因可能是(3)使用JSON语法的规范表示形式-必须保证同一对象产生相同的JSON字符串。属性的确定顺序是执行此操作的必要先决条件。
MarkusSchaber

2
凯文(Kevin),您可以更新此问题的可接受答案吗?
米莉·史密斯

Answers:


255

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

传递JsonProperty一个Order值,串行器将处理其余的事情。

 [JsonProperty(Order = 1)]

这非常类似于

 DataMember(Order = 1) 

System.Runtime.Serialization日子。

这是@ kevin-babcock的重要说明

...仅当您在所有其他属性上将顺序设置为大于1时,才能将顺序设置为1。默认情况下,任何没有“顺序”设置的属性都将被赋予-1的顺序。因此,您必须提供所有序列化的属性和顺序,或将第一个项目设置为-2


97
使用的Order属性JsonPropertyAttribute可用于控制字段序列化/反序列化的顺序。但是,只有在所有其他属性上设置的顺序都大于1时,才能将顺序设置为1。默认情况下,任何不带“顺序”设置的属性都将被赋予-1的顺序。因此,您必须提供所有序列化的属性和顺序,或者将第一个项目设置为-2。
凯文·巴布科克

1
它适用于序列化,但是反序列化时不考虑该顺序。根据文档,order属性用于序列化和反序列化。有解决方法吗?
cangosta 2015年

1
是否有类似的属性JavaScriptSerializer
Shimmy Weitzhandler,2015年

4
@cangosta反序列化的顺序应该无关紧要,除非在某些非常“奇怪”的期望情况下。
user2864740 '16

1
阅读有关在反序列化中尊重Order的愿望的类似github问题讨论:github.com/JamesNK/Newtonsoft.Json/issues/758基本上没有这个机会。
泰斯19th

126

实际上,你可以通过实现控制命令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);

11
这很有用(+1),但有一个警告:字典的序列化似乎没有使用CreateProperties定制。它们可以很好地序列化,但最终不会排序。我认为有一种自定义字典序列化的方法,但是我还没有找到。
可溶性鱼

完善。就是我想要的。谢谢。
韦德·哈特勒

这是一个很好的解决方案。特别适合我,尤其是当并排放置两个JSON对象并使属性对齐时。
文斯

16

就我而言,马蒂亚斯的答案行不通。该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;
    }
}

2
这是使用字典时对我们来说必需的修复程序。
Noocyte

这增加了额外的反序列化和序列化的开销。我添加了一个适用于普通类,词典和ExpandoObject(动态对象)的解决方案
Jay Shah,

11

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

这增加了额外的反序列化和序列化的开销。
杰伊·沙

优秀的解决方案。谢谢。
马来西亚

3

正如Charlie指出的那样,您可以通过对类本身中的属性进行排序来稍微控制JSON属性的排序。不幸的是,这种方法不适用于从基类继承的属性。基类属性将按照它们在代码中的排列顺序进行排序,但将出现在基类属性之前。

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


2

这也适用于普通类,词典和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);

这不是序列化期间的默认排序行为吗?
mr5

1
为了节省其他人的几分钟时间,请注意,尽管有此主张,该答案对字典仍然无效。CreateProperties在字典序列化期间不调用。我探索了JSON.net存储库,以了解究竟哪种机器真正在字典条目中运行。它不会挂接到任何override定制或其他定制中。它只是按原样从对象的枚举器获取条目。看来我必须构造一个SortedDictionarySortedList强制JSON.net来执行此操作。提出的功能建议:github.com/JamesNK/Newtonsoft.Json/issues/2270
William

2

如果您不想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);

0

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

0

实际上,由于我的对象已经是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));

0

如果您控制(即编写)该类,请按字母顺序放置属性,并且在JsonConvert.SerializeObject()调用它们时,它们将按字母顺序进行序列化。


0

我想序列化一个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();
    }
}


-1

如果要使用顺序字段全局配置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?


-5

更新

我刚刚看到了反对派。请参阅下面“史蒂夫”的答案。

原版的

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

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

其中values是引入的IList参数。

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


18
答案简短,但可能已过时。看看史蒂夫的答案(詹姆斯·牛顿·金支持)
布拉德·布鲁斯

-6

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

{ id: 1, name: 'John' }等价于{ name: 'John', id: 1 }(均代表严格等效的对象实例)


12
@Darin-但是序列化中有一个顺序。“ {{id:1,name:'John'}”和“ {{name:'John',id:1}”“作为字符串不同,这就是我在这里关心的。当然,反序列化时对象是等效的。
凯文·蒙特罗斯

1
@Darin-不,不是这种情况。我正在序列化某些内容,然后将其作为字符串传递给仅处理字符串(不支持JSON)的服务,由于多种原因,一个字段首先出现在字符串中会很方便。
凯文·蒙特罗斯

1
它对测试也有好处,能够只查看字符串而不需要反序列化。
史蒂夫

9
稳定的序列化顺序对于缓存验证也很方便。接受字符串的校验和很简单-完整对象图并非如此。
可溶性鱼

1
进行单元测试时,序列化顺序也很方便,因此即使json属性的顺序不同,您也可以轻松地说期望和实际响应字符串相等。
2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.