49

我通常在 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 协议/序列化并能够如上所述详细说明序列化的最佳方法是什么,这样我就不会因为另一台机器尚未更新其版本而中断序列化?

4

4 回答 4

44

对 JSON 进行版本控制的关键是始终添加新属性,而不是删除或重命名现有属性。这类似于协议缓冲区处理版本控制的方式。

例如,如果您从以下 JSON 开始:

{
  "version": "1.0",
  "foo": true
}

并且您想将"foo"属性重命名为"bar",而不仅仅是重命名它。相反,添加一个新属性:

{
  "version": "1.1",
  "foo": true,
  "bar": true
}

由于您永远不会删除属性,因此基于旧版本的客户端将继续工作。这种方法的缺点是 JSON 会随着时间的推移而变得臃肿,并且您必须继续维护旧属性。

向您的客户清楚地定义您的“边缘”案例也很重要。假设您有一个名为 的数组属性"fooList"。该"fooList"属性可以采用以下可能的值:不存在/未定义(该属性实际上不存在于 JSON 对象中,或者它存在并设置为"undefined"null、空列表或具有一个或多个值的列表。客户了解如何表现非常重要,尤其是在未定义/空/空的情况下。

我还建议阅读语义版本控制的工作原理。如果您在版本号中引入语义版本控制方案,则可以在次要版本边界上进行向后兼容的更改,而可以在主要版本边界上进行重大更改(客户端和服务器都必须就相同的主要版本达成一致)。虽然这不是 JSON 本身的属性,但它对于传达版本更改时客户端应该期望的更改类型很有用。

于 2012-12-19T03:26:18.357 回答
16

Google 的基于 java 的gson 库对 json 具有出色的版本控制支持。如果您正在考虑采用 Java 方式,它可能会非常方便。

这里有很好的简单教程。

于 2012-04-06T11:34:17.410 回答
7

不管你使用什么序列化协议,版本 API 的技术通常是相同的。

一般你需要:

  1. 消费者向生产者传达它接受的 API 版本的一种方式(尽管这并不总是可能的)
  2. 生产者将版本控制信息嵌入到序列化数据中的一种方式
  3. 处理未知字段的向后兼容策略

在 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)中,数据应该是自描述的,换句话说,字段名称应该始终与数据一起存储;您不应依赖特定职位上可用的特定数据。

于 2012-12-19T12:23:55.263 回答
6

不要使用 DataContractJsonSerializer,顾名思义,通过这个类处理的对象必须:

a) 标有 [DataContract] 和 [DataMember] 属性。

b) 严格遵守已定义的“合同”,即其所定义的不多也不少。任何额外或缺失的 [DataMember] 都会使反序列化抛出异常。

如果您想足够灵活,那么如果您想选择便宜的选项,请使用 JavaScriptSerializer ......或者使用这个库:

http://json.codeplex.com/

这将使您对 JSON 序列化有足够的控制。

想象一下,你有一个早期的物体。

public class Customer
{ 
    public string Name;

    public string LastName;
}

序列化后将如下所示:

{名称:“约翰”,姓氏:“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;
}

然后稍后您可以访问此元数据并使用它做任何您可能需要的事情。

于 2012-12-18T08:15:49.190 回答