Trabajar con objetos JSON complejos en Swift (codificable)

Me impulsó a escribir este artículo por un ataque de nervios causado por mi deseo de aprender a comunicarme con API de terceros, ¡estaba específicamente interesado en el proceso de decodificación de documentos JSON! Afortunadamente, evité un ataque de nervios, así que ahora es el momento de contribuir a la comunidad e intentar publicar mi primer artículo sobre Habré.





¿Por qué hay problemas con una tarea tan simple?





Para comprender de dónde provienen los problemas, primero debe hablar sobre el conjunto de herramientas que utilicé. Para decodificar objetos JSON, utilicé el protocolo sintetizado relativamente nuevo de la biblioteca Foundation: odable .





Codable es un protocolo integrado que le permite codificar en un objeto de formato de texto y decodificar a partir de un formato de texto. Codificable es la suma de otros dos protocolos: Decodificable y Codificable.





Vale la pena hacer una reserva de que un protocolo se llama sintetizado cuando algunos de sus métodos y propiedades tienen una implementación predeterminada. ¿Por qué se necesitan tales protocolos? Para facilitar el trabajo con la firma, al menos reduciendo la cantidad de código repetitivo.





¡Estos protocolos también le permiten trabajar con la composición, no con la herencia!





Ahora hablemos de los 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)
    }
}
      
      



Eso es todo, ahora que la instancia de JSONDecoder. Llamará a decode () - debajo del capó usará nuestro inicializador para lidiar con la decodificación





Resumiendo, me gustaría decir que trabajar con la red en el desarrollo de iOS está lleno de varias "sorpresas", así que si puedes agregar algo en los comentarios, ¡asegúrate de hacerlo!





¡Gracias a todos!





PD Después de un tiempo, se concluyó que está bien mapear la estructura final en el código usando solo el comportamiento integrado de Codable. La conclusión se hizo luego de ver la sesión de WWDC analizando el trabajo con los datos recibidos de la red.





Enlace de sesión








All Articles