Swift(Codable)での複雑なJSONオブジェクトの操作

サードパーティのAPIとの通信方法を学びたいという私の願望によって引き起こされたほとんど神経衰弱によってこの記事を書くように促されました。私は、JSONドキュメントをデコードするプロセスに特に興味がありました。幸い、神経衰弱を避けたので、今度はコミュニティに貢献して、Habréに関する最初の記事を公開してみてください。





なぜこのような単純なタスクに問題があるのでしょうか。





問題がどこから来ているのかを理解するには、最初に私が使用したツールキットについて話す必要があります。JSONオブジェクトをデコードするために、Foundationライブラリの比較的新しい合成プロトコルであるodableを使用しました





Codableは、テキスト形式のオブジェクトへのエンコードとテキスト形式からのデコードを可能にする組み込みプロトコルです。Codableは、他の2つのプロトコル(DecodableとEncodable)の合計です。





一部のメソッドとプロパティにデフォルトの実装がある場合、プロトコルが合成と呼ばれることを予約する価値があります。なぜそのようなプロトコルが必要なのですか?少なくとも定型コードの量を減らすことによって、それらに署名する作業を容易にするため。





これらのプロトコルを使用すると、継承ではなく構成を操作することもできます。





それでは、問題について話しましょう。





  • まず、すべての新しいものと同様に、このプロトコルはAppleのドキュメントでは十分に説明されていません。説明が不十分とはどういう意味ですか?JSONオブジェクトを操作する最も単純なケースが分析されます。プロトコルのメソッドとプロパティについて簡単に説明します。





  • 第二に、言語の壁の問題。検索クエリの書き方を理解するのに何時間もかかりました。この時の神経は電光石火の速さで尽きました。





  • 第三に、単純な方法でグーグルで検索されたものは、複雑なケースについては教えてくれません。





それでは、特定のケースについて話しましょう。ケースは次のとおりです。FlickrAPIを使用して、キーワードでN枚の写真を検索し(キーワード:検索クエリを読み取ります)、画面に表示します。





最初は、すべてが標準です。APIのキーを取得し、APIドキュメントで必要なRESTメソッドを探し、リソースへのリクエスト引数の説明を確認し、GETリクエストを作成して送信します。





Flickr JSON :





{
   "photos":{
      "page":1,
      "pages":"11824",
      "perpage":2,
      "total":"23648",
      "photo":[
         {
            "id":"50972466107",
            "owner":"191126281@N@7" ,
            "secret":"Q6f861f8b0",
            "server":"65535",
            "farm":66,
            "title":"Prompt & Reliable Electrolux Oven Repairs in Gold Coast",
            "ispublic":1,
            "isfriend":0,
            "isfamily":0
         },
         {
            "id":"50970556873",
            "owner":"49965961@NG0",
            "secret":"21f7a6424b",
            "server":"65535",
            "farm" 66,
            "title":"IMG_20210222_145514",
            "ispublic":1,
            "isfriend":0,
            "isfamily":0
         }
      ]
   },
   "stat":"ok"
}

      
      



, . ? , . ? - , ( , ). - , , , ( ) () Flickr , .





? , GET- , ! : ---. . . JSON- , , . "photo": "id", "secret", "server"





, :





struct Photo {
    let id: String
    let secret: String
    let server: String
}

let results: [Photos] = // ...
      
      



"" . , best practices JSON- .





. , . , Codable. , .





{
   "id":"50972466107",
   "owner":"191126281@N07",
   "secret":"06f861f8b0"
}
      
      



. , JSON- ( "id", "secret", "server"); , (, ). Decodable, , ( , ). ? , " ". . ( , Data, decode(...) JSONDecoder Data).





:





  • , , API - jsonplaceholder.typicode.com, JSON-, GET-.





  • jsonformatter.curiousconcept.com . "" REST , Playground Xcode.





  • tool - app.quicktype.io - Swift JSON-.





. :





struct Photo: Decodable {
    let id: String
    let secret: String
    let server: String
}

let json = """
{
   "id":"50972466107",
   "owner":"191126281@N07",
   "secret":"06f861f8b0"
}
"""

let data = json.data(using: .utf8)
let results: Photo = try! JSONDecoder().decode(Photo.self, from: data)

      
      



, JSON-, "key" : "sometexthere" Decodable String, run-time. Decodable coerce- ( ).





struct Photo: Decodable {
    let id: Int
    let secret: String
    let server: Int
}

let json = """
{
   "id":"50972466107",
   "owner":"191126281@N07",
   "secret":"06f861f8b0"
}
"""

let data = json.data(using: .utf8)
let results: Photo = try! JSONDecoder().decode(Photo.self, from: data)

      
      



. ?





    {
       "id":"50972466107",
       "owner":"191126281@N07",
       "secret":"06f861f8b0",
       "server":"65535",
       "farm":66,
       "title":"Prompt & Reliable Electrolux Oven Repairs in Gold Coast",
       "ispublic":1,
       "isfriend":0,
       "isfamily":0
    }
      
      



, Decodable , , " " , . , API , " " , , , , . - "".





. JSON-:





[
    {
       "id":"50972466107",
       "owner":"191126281@N07",
       "secret":"06f861f8b0",
       "server":"65535",
       "farm":66,
       "title":"Prompt & Reliable Electrolux Oven Repairs in Gold Coast",
       "ispublic":1,
       "isfriend":0,
       "isfamily":0
    },
    {
       "id":"50970556873",
       "owner":"49965961@N00",
       "secret":"21f7a6524b",
       "server":"65535",
       "farm":66,
       "title":"IMG_20210222_145514",
       "ispublic":1,
       "isfriend":0,
       "isfamily":0
    }
]
      
      



( ) . , - !





struct Photo: Decodable {
    let id: String
    let secret: String
    let server: String
}

let json = """
[
    {
       "id":"50972466107",
       "owner":"191126281@N07",
       "secret":"06f861f8b0",
       "server":"65535",
       "farm":66,
       "title":"Prompt & Reliable Electrolux Oven Repairs in Gold Coast",
       "ispublic":1,
       "isfriend":0,
       "isfamily":0
    },
    {
       "id":"50970556873",
       "owner":"49965961@N00",
       "secret":"21f7a6524b",
       "server":"65535",
       "farm":66,
       "title":"IMG_20210222_145514",
       "ispublic":1,
       "isfriend":0,
       "isfamily":0
    }
]
"""

let data = json.data(using: .utf8)
let results: [Photo] = try! JSONDecoder().decode([Photo].self, from: data)
      
      



, [Photo] - Swift. : - !





, , .





" " Decodable , JSON.





  • JSON- - , . - , . , JSON- ! , -, Character , JSON- .





  • JSON - . ? , , : JSON (, ). ? , ( )





  • Decodable .





, . Decodable generic enum CodingKeys, () , JSON , , , ! , . , , : JSON- snake case , Swift camel case. ?





struct Photo: Decodable {
    let idInJSON: String
    let secretInJSON: String
    let serverInJSON: String
    
    enum CodingKeys: String, CodingKey {
        case idInJSON = "id_in_JSON"
        case secretInJSON = "secret_in_JSON"
        case serverInJSON = "server_in_JSON"
    }
}

      
      



rawValue CodingKeys , JSON-!





! JSON !





{
   "photos":{
      "page":1,
      "pages":"11824",
      "perpage":2,
      "total":"23648",
      "photo":[
         {
            "id":"50972466107",
            "owner":"191126281@N@7" ,
            "secret":"Q6f861f8b0",
            "server":"65535",
            "farm":66,
            "title":"Prompt & Reliable Electrolux Oven Repairs in Gold Coast",
            "ispublic":1,
            "isfriend":0,
            "isfamily":0
         },
         {
            "id":"50970556873",
            "owner":"49965961@NG0",
            "secret":"21f7a6424b",
            "server":"65535",
            "farm" 66,
            "title":"IMG_20210222_145514",
            "ispublic":1,
            "isfriend":0,
            "isfamily":0
         }
      ]
   },
   "stat":"ok"
}
      
      



:





  • , : "photos", "stat"





  • "photos" : "page", "pages", "perpages", "total", "photo"





  • "photo" - , .





?





  • . dummy . !





  • Decodable , CodingKeys! ! : Swift ( !) extension stored properties, computed properties odable/Encodable/Decodable, JSON .





, , : photos photo c





, !





// (1)   Photo      .
struct Photo: Decodable {
    let id: String
    let secret: String
    let server: String
}

// (2)  JSONContainer,          .
struct JSONContainer: Decodable {
    // (3) photos  c   "photos"  JSON,    ,        ,     - ,     photo!
    let photos: [Photo]
}

extension JSONContainer {
    // (4)  CodingKeys  .
    enum CodingKeys: String, CodingKey {
        case photos
        // (5)      ,       photos.
        // (6)    -  ,   PhotosKeys -    ,        photos
        enum PhotosKeys: String, CodingKey {
            // (7)      "photo"
            case photoKey = "photo"
        }
    }
    // (8)   
    init(from decoder: Decoder) throws {
        // (9)   JSON,      ,        - photos
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // (10)    (nested - )  photos         
        let photosContainer = try container.nestedContainer(keyedBy: CodingKeys.PhotosKeys.self, forKey: .photos)
        // (11)    
        // (12)        photos -,       .photoKey (.photoKey.rawValue == "photo")
        photos = try photosContainer.decode([Photo].self, forKey: .photoKey)
    }
}
      
      



これで、JSONDecoderのインスタンスになりました。デコード()を呼び出します-内部では、初期化子を使用してデコードを処理します





要約すると、iOS開発でネットワークを操作することはさまざまな「驚き」に満ちていると言いたいので、コメントに何かを追加できる場合は、必ずそれを実行してください。





ありがとうございます!





PSしばらくして、Codableの組み込み動作のみを使用して、コードの最終構造にマップしても問題ないと結論付けられました。結論は、ネットワークから受信したデータを使用して作業を分析するWWDCセッションを見た後に行われました。





セッションリンク








All Articles