ユニバーサルJSONDecoder

現時点では、モバイルアプリケーションの大部分はクライアントサーバーです。 読み込み、同期、イベントの送信が行われるすべての場所で、サーバーと対話する主な方法は、json形式を使用してデータを交換することです。


キー復号化


財団には、データのシリアル化とディデラミネーションのための2つのメカニズムがあります。 古いはNSJsonSerialization 、新しいはCodableです。 利点のリストの最後には、 CodableEncodableDecodable )を実装する構造(またはクラス)とデータをデコードするためのDecodable基づくjsonデータのキーの自動生成などの素晴らしいものがあります。


そして、すべてがうまくいくようで、使用して楽しむことができますが、現実はそれほど単純ではありません。
サーバーでは、次の形式のjsonを見ることができます。


 {"topLevelObject": { "underlyingObject": 1 }, "Error": { "ErrorCode": 400, "ErrorDescription": "SomeDescription" } } 

これは、プロジェクトサーバーの1つからのほぼ実際の例です。


JsonDecoderクラスでは、snake_caseキーを使用して作業を指定できますが、UpperCamelCase、dash-snake-case、または一般的なhodgepodgeがあり、キーを手動で記述したくない場合はどうでしょうか。


幸いなことに、AppleはCodingKeysを使用してCodingKeys構造と照合する前にキーマッピングを構成する機能を提供しJSONDecoder.KeyDecodingStrategy 。 これが使用するものです。


最初に、 CodingKeyプロトコルを実装する構造を作成します。これは、標準ライブラリには何もないためです。


  struct AnyCodingKey: CodingKey { var stringValue: String var intValue: Int? init(_ base: CodingKey) { self.init(stringValue: base.stringValue, intValue: base.intValue) } init(stringValue: String) { self.stringValue = stringValue } init(intValue: Int) { self.stringValue = "\(intValue)" self.intValue = intValue } init(stringValue: String, intValue: Int?) { self.stringValue = stringValue self.intValue = intValue } } 

次に、キーの各ケースを個別に処理する必要があります。 主なもの:
snake_case、dash-snake-case、lowerCamelCaseおよびUpperCamelCase。 チェック、実行、すべてが機能します。


次に、かなり予想される問題に遭遇します:camelCase'ahの略語(多数のidIdID思い出してください)。 それを機能させるには、それらを正しく変換し、ルールを導入する必要があります- 略語はキャメルケースに変換され、最初の文字は大文字のままmyABBRKeyがmyAbbrKeyに変わります。


このソリューションは、いくつかのケースの組み合わせに適しています。


注:実装は、 .customキーデコード戦略に提供されます。


 static func convertToProperLowerCamelCase(keys: [CodingKey]) -> CodingKey { guard let last = keys.last else { assertionFailure() return AnyCodingKey(stringValue: "") } if let fromUpper = convertFromUpperCamelCase(initial: last.stringValue) { return AnyCodingKey(stringValue: fromUpper) } else if let fromSnake = convertFromSnakeCase(initial: last.stringValue) { return AnyCodingKey(stringValue: fromSnake) } else { return AnyCodingKey(last) } } 

日付デコード


次の日常的な問題は、日付が渡される方法です。 サーバーには多くのマイクロサービスがあり、コマンドがわずかに少ないだけでなく、適切な量であるため、「はい、標準を使用します」などの多くの日付形式に直面しています。 さらに、誰かが文字列で日付を渡し、誰かがエポック時間で渡します。 その結果、string-number-timezone-millisecond-separatorの組み合わせの寄せDateDecoder 、iOSのDateDecoderで問題が発生し、厳密な日付形式が必要になります。 ここでの解決策は簡単です。検索するだけで特定の形式の兆候を探し、それらを組み合わせて必要な結果を得ることができます。 これらのフォーマットは私のケースを完全にカバーしました。


注:これは、カスタムDateFormatter初期化子です。 作成されたフォーマッタにフォーマットを設定するだけです。


 static let onlyDate = DateFormatter(format: "yyyy-MM-dd") static let full = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss.SSSx") static let noWMS = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ssZ") static let noWTZ = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss.SSS") static let noWMSnoWTZ = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss") 

JSONDecoder.DateDecodingStrategyを使用してこれをデコーダーにアタッチし、ほとんどすべてを処理し、私たちにとって消化可能な形式に変換するデコーダーを取得します。


性能試験


テストは、サイズが7944バイトのJSON文字列に対して実行されました。


convertFromSnakeCase戦略anyCodingKey戦略
絶対0.001700.00210
相対81%100%

ご覧のとおり、jsonの各キーを強制的にチェックして変換する必要があるため、カスタムDecoder 20%遅くなります。 ただし、これはCodableを実装してデータ構造のキーを明示的に登録する必要がないため、少額の費用です。 このデコーダーの追加により、プロジェクトでボイラープレートの数が大幅に削減されました。 開発者の時間を節約するために使用する必要がありますが、パフォーマンスは低下しますか? それはあなた次第です。


githubライブラリの完全なサンプルコード


英語の記事



Source: https://habr.com/ru/post/J449870/


All Articles