ユニバーサルJSONDecoder

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







キー復号化



財団には、データのシリアル化とディデラミネーションのための2つのメカニズムがあります。 古いはNSJsonSerialization



、新しいはCodable



です。 利点のリストの最後には、 Codable



Encodable



Decodable



)を実装する構造(またはクラス)とデータをデコードするための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の略語(多数のid



Id



ID



思い出してください)。 それを機能させるには、それらを正しく変換し、ルールを導入する必要があります- 略語はキャメルケースに変換され、最初の文字は大文字のまま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.00170 0.00210
相対 81% 100%


ご覧のとおり、jsonの各キーを強制的にチェックして変換する必要があるため、カスタムDecoder



20%遅くなります。 ただし、これはCodable



を実装してデータ構造のキーを明示的に登録する必要がないため、少額の費用です。 このデコーダーの追加により、プロジェクトでボイラープレートの数が大幅に削減されました。 開発者の時間を節約するために使用する必要がありますが、パフォーマンスは低下しますか? それはあなた次第です。







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







英語の記事








All Articles