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、空のリスト、または 1 つ以上の値を持つリスト。特に未定義/null/空の場合に、クライアントがどのように動作するかを理解することが重要です。

また、セマンティック バージョニングの仕組みについても読むことをお勧めします。バージョン番号にセマンティック バージョニング スキームを導入すると、下位互換性のある変更をマイナー バージョンの境界で行うことができ、破壊的変更をメジャー バージョンの境界で行うことができます (クライアントとサーバーの両方が同じメジャー バージョンに同意する必要があります)。 )。これは JSON 自体のプロパティではありませんが、バージョンが変更されたときにクライアントが予期する必要がある変更の種類を伝えるのに役立ちます。

于 2012-12-19T03:26:18.357 に答える
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;
}

シリアル化すると、次のようになります。

{ 名前: "ジョン", 姓: "ドウ" }

オブジェクト定義を変更してフィールドを追加/削除する場合。JavaScriptSerializerなどを利用するとスムーズにデシリアライズが行われます。

public class Customer
{ 
    public string Name;

    public string LastName;

    public int Age;
}

最後の json をこの新しいクラスにデシリアライズしようとしても、エラーはスローされません。問題は、新しいフィールドがデフォルトに設定されることです。この例では: 「Age」はゼロに設定されます。

バージョン番号を含む、すべてのオブジェクトに存在するフィールドを独自の規則に含めることができます。この場合、空のフィールドとバージョンの不一致の違いがわかります。

つまり、クラス 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 に答える