【问题标题】:Order of serialized fields using JSON.NET使用 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,你能更新一下这个问题的接受答案吗?

标签: c# json json.net


【解决方案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 属性的顺序不同,您也可以轻松地说预期响应字符串与实际响应字符串相等。
                  猜你喜欢
                  • 2015-12-10
                  • 1970-01-01
                  • 2015-06-12
                  • 2015-11-07
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多