1

I'm trying to decode a json request using Alamofire 5.2 The problem is that I use JSONDecoder and I have some issues about the conversion

The API is in Spanish and my models in English so I decided to changed this kind of values using an enum of keys

But I don't know if this works... Here's my code:

API RESPONSE: (json variable)

    {
  "sistemaOperativoId" : 0,
  "nombreUsuario" : "Coasnf_09",
  "menus" : [

  ],
  "acciones" : [

  ],
  "fechaRegistro" : "2020-04-15T09:46:24.0573154",
  "empresa" : null,
  "version" : null
}

MY MODEL:

struct UserP: Decodable{
    var username : String
    var company : String

    private enum CodingKeys: String, CodingKey{
        case username = "nombreUsuario"
        case company = "empresa"
    }

    init(from decoder: Decoder) throws{
        let container = try decoder.container(keyedBy: CodingKeys.self)
        username = try container.decode(String.self, forKey: .username) ?? "null"
        company = try container.decode(String.self, forKey: .company)  ?? "null"
    }

    init(username: String, company: String){
        self.username = username
        self.company = company
    }
}

CONVERTION:

   func login(user: User) -> UserP? {
        var userData: UserP! = nil
        AF.request(UserRouter.login(user: user)).responseJSON{ response in
            switch response.result {
            case .success(let response):
                print(response)
                let dict = (response as? [String : Any])!
                let json = dict["data"] as! [String: Any]

                if let jsonData = try? JSONSerialization.data(withJSONObject: json , options: .prettyPrinted)
                {
                    do {
                        var jsonString = String(data: jsonData, encoding: String.Encoding.utf8)!
                        print(jsonString)
                        userData = try JSONDecoder().decode(UserP.self, from: Data(jsonString.utf8))
                        print("Object Converted: ", userData.username)
                    } catch {
                        print("Parsing Failed: ", error.localizedDescription)
                    }
                }



                break

            case .failure(let error):
                print(error)
                break
            }
        }

        return userData
    }
Antonio Labra
  • 1,694
  • 2
  • 12
  • 21
  • What is `User`? Where is `json` being set? Why do you use both `JSONSerialization` _and_ `JSONDecoder`? – Gereon Apr 17 '20 at 19:01
  • `print("Parsing Failed: ", error.localizedDescription)` => ùprint("Parsing Failed: ", error) – Larme Apr 17 '20 at 19:01
  • @Larme "Parsing Failed: No se pudo leer los datos porque no se encontraron." – Antonio Labra Apr 17 '20 at 19:08
  • @Gereon "User" doesn't really matter because I used it to get json in a response – Antonio Labra Apr 17 '20 at 19:10
  • 1
    Show the code where you get `json`, then. The encoding to a string and then decoding to a `UserP` seems redundant - why can't you simply pass the JSON data you get to `JSONDecoder.decode()`? – Gereon Apr 17 '20 at 19:11
  • Because I have another Structure and I converted into a simple structure that you can find in "API RESPONSE" section. BTW I updated my method – Antonio Labra Apr 17 '20 at 19:13
  • The code is so strange. You are mixing JSONSerialization & JSONDecoder. You are converting like two or three times going from "Data to Swift Dict" and back and so on... What print `print(jsonString)`? And what's do `print(error)` and not localizedDescription. – Larme Apr 17 '20 at 19:17
  • Try to decode jsonString using my model. That's the only problem – Antonio Labra Apr 17 '20 at 19:22
  • you don't need `let dict = (response as? [String : Any])!`, just `let json = response.data` would suffice. But then again I suspect one more problem here `company = try container.decode(String.self, forKey: .company) ?? "null"` – staticVoidMan Apr 17 '20 at 19:22
  • @staticVoidMan I think the problem is on the keys. I found this https://medium.com/flawless-app-stories/lets-parse-the-json-like-a-boss-with-swift-codable-protocol-3d4c4290c104 and I used "Scenario 2" – Antonio Labra Apr 17 '20 at 19:26
  • `CodingKeys` are fine, your decoder logic is not. that is problem #2 that you will face after fixing the Alamofire logic – staticVoidMan Apr 17 '20 at 19:27
  • 1
    if `nombreUsuario` and `empresa` are not guarnateed to come in the json then it should be optional and then your decoder should have something like `company = try container.decodeIfPresent(String.self, forKey: .company)` – staticVoidMan Apr 17 '20 at 19:29
  • It was helpful and works... Thanks a lot! @staticVoidMan – Antonio Labra Apr 17 '20 at 19:37

1 Answers1

1

Alamofire logic related change:

response from Alamofire has a data property that you can use directly:

let json = response.data
do {
    let user = try JSONDecoder().decode(UserP.self, from: json)
    print(user)
}
catch {
    print(error)
}

Furthermoe, if the keys nombreUsuario and empresa are not guaranteed to come in the json then recommended way is to mark those variables as optional. With this you don't need the custom decoder logic.

Model Change #1:

struct UserP: Decodable {
    var username: String?
    var company: String?

    private enum CodingKeys: String, CodingKey{
        case username = "nombreUsuario"
        case company = "empresa"
    }
}

Model Change #2:

If you want to give some default values then a custom decoder can help:

struct UserP: Decodable {
    var username: String
    var company: String

    private enum CodingKeys: String, CodingKey{
        case username = "nombreUsuario"
        case company = "empresa"
    }

    init(from decoder: Decoder) throws{
        let container = try decoder.container(keyedBy: CodingKeys.self)
        username = try container.decodeIfPresent(String.self, forKey: .username) ?? "Default Name"
        company = try container.decodeIfPresent(String.self, forKey: .company) ?? "Default Company Name"
    }
}
staticVoidMan
  • 19,275
  • 6
  • 69
  • 98
  • Thank you so much! – Antonio Labra Apr 17 '20 at 19:39
  • @AntonioLabra My pleasure :) NOTE: if you mark those as optional then you don't need the `init(from decoder: Decoder)` unless you want to give an explicit default value. For completeness sake, I have updated my answer to show both scenarios. – staticVoidMan Apr 17 '20 at 19:47
  • @staticVoidMain talking about Alamofire request how can I fixed it? because it has issues and doesn't return my object converted – Antonio Labra Apr 17 '20 at 21:13
  • @AntonioLabra What are you getting vs what are you expecting? – staticVoidMan Apr 17 '20 at 22:24
  • I'm getting my request but it returns "nil" by first time. It seems like it's not an Async function – Antonio Labra Apr 17 '20 at 22:49
  • @AntonioLabra What returns nil. Anything specific? Any error messages? – staticVoidMan Apr 17 '20 at 22:51
  • Without error messages. The problem is that returns nil and the first time when I launch the function and It's strange because enters when I give the error to the Interface – Antonio Labra Apr 17 '20 at 22:57
  • I'm trying to use "completionHandlers" but I don't know if its possible. I'm having: "Escaping closure captures non-escaping parameter 'completionHandler" – Antonio Labra Apr 17 '20 at 22:57
  • @AntonioLabra If you have to update UI, like reload table or something then you probably need to do that within `DispatchQueue.main.async` – staticVoidMan Apr 17 '20 at 22:59
  • my AF.request()... starts after I have a "nil" result and this returns – Antonio Labra Apr 17 '20 at 23:01
  • @AntonioLabra That sounds like an entirely different problem. Post another question with details specific to the new problem you have. I will take a look at it later :) – staticVoidMan Apr 17 '20 at 23:18