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

ami*_*ros -2 c# serialization json list json.net

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

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

现在,问题是 - 我想改变它QuestionItem并为它添加一些变量.

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

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

小智 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; }
}
Run Code Online (Sandbox Code Playgroud)

您可以通过创建子类来扩展它:

[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; }
}
Run Code Online (Sandbox Code Playgroud)

请注意,使用与类的默认构造函数不同的任何内容都要求您[JsonConstructor]在此构造函数上使用Attribute,并且所述构造函数的每个参数都必须与相应的JSON属性完全相同.否则,您将获得异常,因为没有可用的默认构造函数.

您的WebAPI现在将发送序列化的NewQuestionItems,可以将其反序列化为QuestionItems.实际上:默认情况下,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);
Run Code Online (Sandbox Code Playgroud)

并简单地查看oldQuestionItem调试器中的属性值.

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

如果是这样的话,那么你的对象是不同的,因此,需要用不同的URI完全不同的对象的API中,只要你仍然需要保持对现有URI旧实例.

这将我们带入一般架构:

您正在尝试实现的最干净和简化的方法是正确地对您的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 = ....
}
Run Code Online (Sandbox Code Playgroud)

这将允许您发送其他信息,例如api版本号,api版本发布,指向不同API端点的链接,错误信息而不更改对象类型等.

使用的包装用自定义首部的另一种方法是完全执行HATEOAS约束,这也被广泛使用.两者都可以与正确的版本控制一起为API更改节省大部分麻烦.