【问题标题】:Managing multiple versions of object in JSON在 JSON 中管理多个版本的对象
【发布时间】:2018-02-17 07:59:26
【问题描述】:

我有一个 C# 类,它有许多变量。我们称之为"QuestionItem"。 我有一个该对象的列表,用户对其进行修改,然后通过 JSON 序列化(使用 Newtonsoft JSON 库)将其发送到服务器。 为此,我将服务器中已经存在的对象反序列化为List<QuestionItem>,然后将这个新修改的对象添加到列表中,然后将其序列化回服务器。

为了向用户显示此QuestionItems 列表,我将 JSON 反序列化为我的对象,并将其显示在某处。

现在,问题是 - 我想更改这个 QuestionItem 并添加一些变量。

但我无法将此NewQuestionItem 发送到服务器,因为服务器中的项目类型为OldQuestionItem

如何合并这两种类型,或者将旧类型转换为新类型,而旧版本的用户仍然可以使用应用程序?

【问题讨论】:

  • “虽然使用旧版本的用户仍然可以使用该应用程序” - 这是向后兼容,它是最容易实现/维护的版本控制。由于您没有提供代码,我无法更具体地回答。
  • 如果您只是添加新属性,为什么这甚至会成为问题?只需将这些新属性添加到您的 QuestionItem 类型。对于新的 JSON 对象,它们将被填充,对于旧的,它们不会。
  • 我不明白。 JSON 解码器通常能够很好地处理额外的属性或缺失的属性。也许你应该添加一些代码来展示你实际在做什么,以及仅仅扩展你的类型对你不起作用。
  • 我真的不敢相信,因为我能够运行 JsonConvert.DeserializeObject<List<Test>>("[{},{}]"),对于 Test 的任何定义都很好。显示您的代码。
  • 当然,您不必将 JSON 对象映射到静态类型对象。您可以将其保留为动态 JSON 对象。但仅仅因为做了,并不意味着其他用户会在他们的代码中这样做。你已经说过你不会让那些更新任何东西,所以你不走运。

标签: c# json list serialization json.net


【解决方案1】:

您如何将 OldQuestionItem 包装为 QuestionItem 的属性?例如:

public class NewQuestionItem
{
    public OldQuestionItem OldItem { get; set; }
    public string Property1 {get; set; }
    public string Property2 {get; set; }
    ...
}

这样您可以维护项目的先前版本,同时定义要返回的新信息。

柯达

【讨论】:

    【解决方案2】:

    您正在使用面向对象的语言,因此如果可能,您最好使用继承。

    假设您的旧 QuestionItem 是:

    [JsonObject(MemberSerialization.OptOut)]
    public class QuestionItem 
    {
        [JsonConstructor]
        public QuestionItem(int Id, int Variant)
        {
            this.Id = Id;
            this.Variant = Variant;
        }
    
        public int Id { get; }
        public int Variant { get; }
        public string Name { get; set; }
    }
    

    你可以通过创建一个子类来扩展它:

    [JsonObject(MemberSerialization.OptOut)]
    public class NewQuestionItem : QuestionItem
    {
        private DateTime _firstAccess;
    
        [JsonConstructor]
        public NewQuestionItem(int Id, int Variant, DateTime FirstAccess) : base(Id, Variant)
        {
            this.FirstAccess = FirstAccess;
        }
        public DateTime FirstAccess { get; }
    }
    

    请注意,使用与类的默认构造函数不同的任何内容都需要您在此构造函数上使用[JsonConstructor] 属性,并且该构造函数的每个参数必须与相应的 JSON 属性完全相同。否则你会得到一个异常,因为没有可用的默认构造函数。

    您的 WebAPI 现在将发送序列化的 NewQuestionItems,可以将其反序列化为 QuestionItems。事实上:默认情况下,JSON.NET 与大多数 Json 库一样,如果它们具有 至少一个 共同属性,则会将其反序列化为 any 对象。只需确保您想要序列化/反序列化的对象的任何成员实际上都可以被序列化。

    你可以用下面三行代码来测试上面的例子:

    var newQuestionItem = new NewQuestionItem(1337, 42, DateTime.Now) {Name = "Hello World!"};
    var jsonString = JsonConvert.SerializeObject(newQuestionItem);
    var oldQuestionItem = JsonConvert.DeserializeObject<QuestionItem>(jsonString);
    

    只需在调试器中查看oldQuestionItem 的属性值。

    因此,只要您的 NewQuestionItem 仅向对象添加属性并且既不删除也不修改它们,这是可能的。

    如果是这种情况,那么您的对象是不同的,因此,需要在您的 API 中具有不同 URI 的完全不同的对象,只要您仍然需要在现有 URI 上维护旧实例。

    这将我们带到了一般架构:

    要达到的目标,最简洁、最精简的方法是正确地version 您的 API。

    出于此链接的目的,我假设一个 Asp.NET WebApi,因为您正在处理 C#/.NET 中的 JSON。这允许在不同的版本上调用不同的控制器方法,从而根据实现时间对 API 提供的资源进行结构更改。其他 API 将提供相同或至少相似的功能,或者可以手动实现。

    根据实际对象的数量和大小以及请求和结果集的潜在复杂性,可能还值得研究使用附加信息包装请求或响应。因此,与其请求T 类型的对象,不如请求QueryResult&lt;T&gt; 类型的对象,其定义如下:

    [JsonObject(MemberSerialization.OptOut)]
    public class QueryResult<T>
    {
        [JsonConstructor]
        public QueryResult(T Result, ResultState State, 
                Dictionary<string, string> AdditionalInformation)
        {
            this.Result = result;
            this.State = state;
            this.AdditionalInformation = AdditionalInformation;
        }
    
        public T Result { get; }
        public ResultState State { get; }
        public Dictionary<string, string> AdditionalInformation { get; }
    }
    
    public enum ResultState : byte
    {
        0 = Success,
        1 = Obsolete,
        2 = AuthenticationError,
        4 = DatabaseError,
        8 = ....
    }
    

    这将允许您在不更改对象类型的情况下发送附加信息,例如 api 版本号、api 版本发布、指向不同 API 端点的链接、错误信息等。

    使用带有自定义标头的包装器的替代方法是完全使用implement HATEOAS 约束,这也被广泛使用。两者都可以与适当的版本控制一起为您省去 API 更改的大部分麻烦。

    【讨论】:

      【解决方案3】:

      你可以使用类似的东西

      public class OldQuestionItem
      {
        public DateTime UploadTimeStamp {get; set;} //if less then DateTime.Now then it QuestionItem 
        public string Property1 {get; set; }
        public string Property2 {get; set; }
        ...
      
        public OldQuestionItem(NewQuestionItem newItem)
        {
           //logic to convert new in old
        }
      }
      
      public class NewQuestionItem : OldQuestionItem
      {
      
      }
      

      并使用 UploadTimeStamp 作为标记来了解它是什么问题。

      【讨论】:

        猜你喜欢
        • 2011-04-10
        • 1970-01-01
        • 2010-10-20
        • 1970-01-01
        • 2020-12-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-11
        相关资源
        最近更新 更多