Morozov
Morozov

Reputation: 5250

How to decode a property with type of JSON dictionary in Swift?

I am currently getting the following error in my tests:

enter image description here

If I correctly understood the problem, then I need to convert my dictionary to decoder.

At the moment i have the next code:

static var validConfirmAuthorizationData: [String: Any] { return json("valid_confirm_authorization_data") }

Which has the following structure:

{
    "data": {
        "id": "1",
        "success": true
    }
}

And the response class itself which I use together with the decodable:

public struct SEConfirmAuthorizationResponse: Decodable {
    public let id: String
    public let success: Bool

    enum CodingKeys: String, CodingKey {
        case data
    }

    enum DataCodingKeys: String, CodingKey {
        case id
        case success
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data)
        id = try dataContainer.decode(String.self, forKey: .id)
        success = try dataContainer.decode(Bool.self, forKey: .success)
    }
}

Upvotes: 0

Views: 1492

Answers (3)

Morozov
Morozov

Reputation: 5250

It turned out to solve the problem as follows.

First of all, I created SpecDecodableModel:

public struct SpecDecodableModel<T: Decodable> {
    static func create(from fixture: [String: Any]) -> T {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategyFormatters = [DateUtils.dateFormatter, DateUtils.ymdDateFormatter]
        let fixtureData = Data(fixture.jsonString!.utf8)
        return try! decoder.decode(T.self, from: fixtureData)
    }
}

After that, I can convert my JSON structures to the desired type.

And now returning to the original problem, I can fix it as follows:

let fixture = DataFixtures.validConfirmAuthorizationData
let response = SpecDecodableModel<SEConfirmAuthorizationResponse>.create(from: fixture)

Update:

public extension Dictionary {

    var jsonString: String? {
        if let data = try? JSONSerialization.data(withJSONObject: self, options: []),
            let string = String(data: data, encoding: String.Encoding.utf8) {
            return string
        }
        return nil
    }
}

Upvotes: 0

Ammannaidu Guruvu
Ammannaidu Guruvu

Reputation: 7

public struct AuthorizationData: Codable {
    public let id: String
    public let success: Bool
    
    init(id: String, success: Bool) {
        self.id = id
        self.success = success
    }
    
    enum CodingKeys: String, CodingKey {
        case id
        case success
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)
        success = try container.decode(Bool.self, forKey: .success)
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(success, forKey: .success)
    }
}

public struct SEConfirmAuthorizationResponse: Codable {
    public let data: AuthorizationData
    
    enum CodingKeys: String, CodingKey {
        case data
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        data = try container.decode(AuthorizationData.self, forKey: .data)
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(data, forKey: .data)
    }
    
    init(_ data: AuthorizationData) {
        self.data = data
    }
}

func authData() -> Data? {
    let authorizationData = AuthorizationData(id: "1", success: true)
    let response = SEConfirmAuthorizationResponse(authorizationData)
    
    guard let prettyJsonData = try? JSONEncoder().encode(response) else {
        return nil
    }
    if let jsonString = String(data: prettyJsonData, encoding: .utf8) {
        print("jsonString", jsonString)
    }
    return prettyJsonData
}

func authResponse() {
    if let data = authData() {
        guard let response = try? JSONDecoder().decode(SEConfirmAuthorizationResponse.self, from: data) else {
            return
        }
        print("response", response)
    }
}

Upvotes: 0

Jake
Jake

Reputation: 2216

You have a couple options, you could create a codable struct with a variable named data. Data would have the type SEConfirmAuthorizationResponse. Then you would decode into the new struct.

You could decode into a dictionary like:

let decoded = JsonDecoder().decode([String: SEConfirmAuthorizationResponse].self, from: someData)

let response = decoded[“data”]

Or you could write a custom decoder which I usually try to avoid.

Upvotes: 1

Related Questions