使用 JSON 协议处理版本控制的最佳方法是啥?
Posted
技术标签:
【中文标题】使用 JSON 协议处理版本控制的最佳方法是啥?【英文标题】:What is the best way to handle versioning using JSON protocol?使用 JSON 协议处理版本控制的最佳方法是什么? 【发布时间】:2012-04-06 11:20:40 【问题描述】:我通常在 C# 中编写代码的所有部分,并且在编写序列化协议时,我使用 FastSerializer 快速高效地序列化/反序列化类。它也很容易使用,并且相当直接地进行“版本控制”,即处理不同版本的序列化。我通常使用的东西是这样的:
public override void DeserializeOwnedData(SerializationReader reader, object context)
base.DeserializeOwnedData(reader, context);
byte serializeVersion = reader.ReadByte(); // used to keep what version we are using
this.CustomerNumber = reader.ReadString();
this.HomeAddress = reader.ReadString();
this.ZipCode = reader.ReadString();
this.HomeCity = reader.ReadString();
if (serializeVersion > 0)
this.HomeAddressObj = reader.ReadUInt32();
if (serializeVersion > 1)
this.County = reader.ReadString();
if (serializeVersion > 2)
this.Muni = reader.ReadString();
if (serializeVersion > 3)
this._AvailableCustomers = reader.ReadList<uint>();
和
public override void SerializeOwnedData(SerializationWriter writer, object context)
base.SerializeOwnedData(writer, context);
byte serializeVersion = 4;
writer.Write(serializeVersion);
writer.Write(CustomerNumber);
writer.Write(PopulationRegistryNumber);
writer.Write(HomeAddress);
writer.Write(ZipCode);
writer.Write(HomeCity);
if (CustomerCards == null)
CustomerCards = new List<uint>();
writer.Write(CustomerCards);
writer.Write(HomeAddressObj);
writer.Write(County);
// v 2
writer.Write(Muni);
// v 4
if (_AvailableCustomers == null)
_AvailableCustomers = new List<uint>();
writer.Write(_AvailableCustomers);
所以很容易添加新东西,或者如果选择完全改变序列化。
但是,我现在想使用 JSON 的原因与此处无关 =) 我目前正在使用 DataContractJsonSerializer,我现在正在寻找一种方法来获得与上面使用 FastSerializer 相同的灵活性.
所以问题是;创建 JSON 协议/序列化并能够如上所述详细说明序列化的最佳方法是什么,这样我就不会因为另一台机器尚未更新其版本而中断序列化?
【问题讨论】:
【参考方案1】:对 JSON 进行版本控制的关键是始终添加新属性,而不是删除或重命名现有属性。这类似于how protocol buffers handle versioning。
例如,如果您从以下 JSON 开始:
"version": "1.0",
"foo": true
并且您想将"foo"
属性重命名为"bar"
,不要只是重命名它。相反,添加一个新属性:
"version": "1.1",
"foo": true,
"bar": true
由于您永远不会删除属性,因此基于旧版本的客户端将继续工作。这种方法的缺点是 JSON 会随着时间的推移而变得臃肿,并且您必须继续维护旧属性。
向客户明确定义“边缘”案例也很重要。假设您有一个名为"fooList"
的数组属性。 "fooList"
属性可以采用以下可能的值:不存在/未定义(该属性实际上不存在于 JSON 对象中,或者它存在并设置为 "undefined"
)、null
、空列表或列出一个或多个值。让客户了解行为方式非常重要,尤其是在未定义/空/空的情况下。
我还建议阅读semantic versioning 的工作原理。如果您在版本号中引入语义版本控制方案,则可以在次要版本边界上进行向后兼容的更改,而可以在主要版本边界上进行重大更改(客户端和服务器都必须就相同的主要版本达成一致)。虽然这不是 JSON 本身的属性,但这对于传达版本更改时客户端应该期望的更改类型很有用。
【讨论】:
那么是否禁止删除 JSON 节点中的属性?如果我的班级确实删除了一些变量怎么办? JSON 并不禁止您删除属性。但是,如果客户端使用该 JSON,并且某个属性突然消失,则该客户端可能会中断。版本控制策略的目标是允许 API 发展,同时保持客户端稳定。【参考方案2】:Google 的基于 java 的 gson library 对 json 具有出色的版本控制支持。如果您正在考虑采用 Java 方式,这可能会非常方便。
有很好的简单教程here。
【讨论】:
我是用 C# 编写的,但应该可以用任何语言实现,否则它有点错过了重点......【参考方案3】:不管你使用什么序列化协议,版本 API 的技术通常是相同的。
一般你需要:
-
消费者向生产者传达其接受的 API 版本的一种方式(尽管这并不总是可行)
生产者将版本控制信息嵌入序列化数据的一种方式
一种处理未知字段的向后兼容策略
在 Web API 中,消费者接受的 API 版本通常嵌入在 Accept 标头中(例如,Accept: application/vnd.myapp-v1+json application/vnd.myapp-v2+json
表示消费者可以处理您的 API 的版本 1 和版本 2)或在 URL 中不太常见(例如https://api.twitter.com/1/statuses/user_timeline.json
)。这通常用于主要版本(即向后不兼容的更改)。如果服务器和客户端没有匹配的 Accept 标头,则通信失败(或在尽力而为的基础上继续进行或回退到默认基线协议,具体取决于应用程序的性质)。
然后,生产者在请求的版本之一中生成序列化数据,然后将此版本信息嵌入到序列化数据中(例如,作为名为 version
的字段)。消费者应该使用嵌入在数据中的版本信息来确定如何解析序列化数据。数据中的版本信息还应包含次要版本(即用于向后兼容的更改),通常消费者应该能够忽略次要版本信息并仍然正确处理数据,尽管理解次要版本可能允许客户端对如何处理数据。
处理未知字段的常用策略类似于如何解析 html 和 CSS。当消费者看到未知字段时,他们应该忽略它,并且当数据缺少客户端期望的字段时,它应该使用默认值;根据通信的性质,您可能还需要指定一些必填字段(即缺少字段被视为致命错误)。在次要版本中添加的字段应始终是可选字段;次要版本可以添加可选字段或更改字段语义,只要它向后兼容即可,而主要版本可以删除字段或添加必填字段或以向后不兼容的方式更改字段语义。
在可扩展的序列化格式(如 JSON 或 XML)中,数据应该是自描述的,换句话说,字段名称应该始终与数据一起存储;您不应该依赖特定职位上可用的特定数据。
【讨论】:
【参考方案4】:不要使用DataContractJsonSerializer,顾名思义,通过这个类处理的对象将不得不:
a) 使用 [DataContract] 和 [DataMember] 属性进行标记。
b) 严格遵守已定义的“合同”,即其所定义的不多不少。任何额外或缺失的 [DataMember] 都会使反序列化抛出异常。
如果您想足够灵活,那么如果您想选择便宜的选项,请使用 javascriptSerializer...或者使用这个库:
http://json.codeplex.com/
这将使您能够充分控制 JSON 序列化。
想象一下你有一个早期的对象。
public class Customer
public string Name;
public string LastName;
序列化后会如下所示:
姓名:“John”,姓氏:“Doe”
如果您更改对象定义以添加/删除字段。如果使用 JavaScriptSerializer 等,反序列化将顺利进行。
public class Customer
public string Name;
public string LastName;
public int Age;
如果您尝试将最后一个 json 反序列化为这个新类,则不会抛出错误。问题是您的新字段将设置为默认值。在此示例中:“年龄”将设置为零。
您可以在自己的约定中包含所有对象中存在的字段,该字段包含版本号。在这种情况下,您可以区分空字段或版本不一致。
可以说:您的类 Customer v1 已序列化:
Version: 1, LastName: "Doe", Name: "John"
您想反序列化为 Customer v2 实例,您将拥有:
Version: 1, LastName: "Doe", Name: "John", Age: 0
您可以通过某种方式检测对象中的哪些字段可靠,哪些不可靠。在这种情况下,您知道您的 v2 对象实例来自 v1 对象实例,因此不应信任字段 Age。
我记得您还应该使用自定义属性,例如“MinVersion”,并用支持的最低版本号标记每个字段,所以你会得到这样的结果:
public class Customer
[MinVersion(1)]
public int Version;
[MinVersion(1)]
public string Name;
[MinVersion(1)]
public string LastName;
[MinVersion(2)]
public int Age;
然后您可以访问此元数据并使用它做任何您可能需要的事情。
【讨论】:
感谢您的提示。但是,除了非常臃肿的“ShouldSerialize”方法之外,我没有看到任何特定的版本处理。你是这么想的吗? @Ted,我只是说 (a) 这些序列化程序足够灵活,可以处理任何输入。 (b) 如果你有这样的灵活性,那么处理版本控制就不那么重要了。检查我的编辑。 问题是 C# 上的属性很好,但这是由 android 中的 JAVA 实现读取的,它们没有属性。 @Ted,纯 json 将传输到您的 Android 程序,根本没有属性。所以不会读取任何属性 关于在.NET 4.8中使用DataContractJsonSerializer
:a) 是错误的,你可以只用[Serializable]
进行序列化,所有的props和fields都会被序列化; b) 错误,添加或删除[DataMember]
属性不会干扰反序列化,只有类型更改才会这样做。以上是关于使用 JSON 协议处理版本控制的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
处理发布到 Nodejs 服务器的 JSON 消息数组的最佳方法是啥?