您正在使用面向对象的语言,因此如果可能,您最好使用继承。
假设您的旧 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<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 更改的大部分麻烦。