在JSON中管理多个版本的对象

时间:2017-09-08 11:40:24

标签: c# json list serialization json.net

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

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

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

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

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

3 个答案:

答案 0 :(得分:5)

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

假设您的旧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现在将发送序列化的NewQuestionItem,可以将其反序列化为QuestionItem。实际上:默认情况下,JSON.NET与大多数Json库一样,如果它们共有至少一个属性,则会将其反序列化为任何对象。只需确保您要序列化/废弃的对象的任何成员实际上都可以序列化。

您可以使用以下三行代码测试上述示例:

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<T>的对象,并按以下方式定义:

[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更改的麻烦。

答案 1 :(得分:0)

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

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

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

答案 2 :(得分:0)

您可以使用类似

的内容
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作为标记来理解它是什么问题。