Trabalho com objetos JSON complexos em Swift (codificável)

Fui solicitado a escrever este artigo por um colapso quase nervoso causado pelo meu desejo de aprender como me comunicar com APIs de terceiros. Eu estava especificamente interessado no processo de decodificação de documentos JSON! Felizmente, evitei um colapso nervoso, então agora é hora de contribuir com a comunidade e tentar publicar meu primeiro artigo sobre Habré.





Por que existem problemas com uma tarefa tão simples?





Para entender de onde vêm os problemas, primeiro você precisa falar sobre o kit de ferramentas que usei. Para decodificar objetos JSON, usei o protocolo sintetizado relativamente novo da biblioteca Foundation - odable .





Codable é um protocolo integrado que permite codificar em um objeto de formato de texto e decodificar em um formato de texto. Codable é a soma de dois outros protocolos: Decodable e Encodable.





Vale ressaltar que um protocolo é denominado sintetizado quando alguns de seus métodos e propriedades possuem uma implementação padrão. Por que esses protocolos são necessários? Para tornar mais fácil trabalhar com a assinatura deles, pelo menos reduzindo a quantidade de código clichê.





Esses protocolos também permitem que você trabalhe com composição, não com herança!





Agora vamos falar sobre os problemas:





  • -, , Apple. " "? JSON ; .





  • -, . , . .





  • -,





. : API Flickr, N- ( : ) .





: API, REST- API, , c 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)
    }
}
      
      



É isso, agora que a instância do JSONDecoder. Chamará decode () - nos bastidores, usará nosso inicializador para lidar com a decodificação





Resumindo, gostaria de dizer que trabalhar com a rede no desenvolvimento do iOS é recheado de várias "surpresas", então se você puder acrescentar algo nos comentários - não deixe de fazê-lo!





Obrigado a todos!





PS Depois de algum tempo, concluiu-se que não há problema em mapear a estrutura final no código usando apenas o comportamento embutido de Codable. A conclusão foi feita após assistir à sessão do WWDC analisando o trabalho com os dados recebidos da rede.





Link da sessão








All Articles