skip to Main Content

I have the following response from JSON service:

[
    {
        "id": 1,
        "name": "Student"
    },
    {
        "id": 2,
        "name": "Faculty"
    }
]

And I want to map it to an enum:

enum Role: Int, Codable, Identifiable {
    case student
    case faculty
    
    var id: Int {
        rawValue
    }
}

But I am having trouble mapping since the array in JSON contains dictionaries.

2

Answers


  1. Your objects have 2 member : id which is an Int and name which is a string that you want to use an enum for it :

    struct Role: Codable, Identifiable {
        enum RoleName: String, Codable {
            case student = "Student"
            case faculty = "Faculty"
        }
        var name: RoleName
        var id: Int
    }
    

    example for decoding :

    let json = """
    [
        {
            "id": 1,
            "name": "Student"
        },
        {
            "id": 2,
            "name": "Faculty"
        }
    ]
    """
    
    var roles = [Roles]()
    if let data = json.data(using: .utf8) {
       roles = try? JSONDecoder().decode([Role].self, from: data)
    }
    

    Edit: corrected type of name + coding key for enum

    Login or Signup to reply.
  2. What you need is a custom encoder and decoder. Another issue in your code is that you are not specifying the integer value of your enumeration cases which should start with 1 (if you don’t specify it will start with zero). So your enumeration should look like this:

    enum Role: Int, Codable, Identifiable {
        case student = 1, faculty
        var id: RawValue { rawValue }
        private enum CodingKeys: String, CodingKey {
            case id, name
        }
    }
    

    Then you can create a custom decoder:

    extension Role {
        public init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            let id = try container.decode(Int.self, forKey: .id)
            let name = try container.decode(String.self, forKey: .name)
            switch (id, name) {
            case (1, "Student"):
                self = .student
            case (2, "Faculty"):
                self = .faculty
            default:
                throw DecodingError.dataCorrupted(
                    .init(
                        codingPath: decoder.codingPath,
                        debugDescription: "Invalid Role data"
                    )
                )
            }
        }
    }
    

    And if you need to encode it as well a custom encoder:

    extension Role {
        public func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(id, forKey: .id)
            switch self {
            case .student:
                try container.encode("Student", forKey: .name)
            case .faculty:
                try container.encode("Faculty", forKey: .name)
            }
        }
    }
    

    Playground testing:

    let jsonData = Data(#"[{"id":1,"name":"Student"},{"id":2,"name":"Faculty"}]"#.utf8)
    let roles: [Role] = [
        .init(rawValue: 1)!,
        .init(rawValue: 2)!
    ]
    let encodedRoles = try JSONEncoder().encode(roles)
    print(encodedRoles == jsonData)  // "truen"
    do {
        let decodedRoles = try JSONDecoder().decode([Role].self, from: jsonData)
        print(roles == decodedRoles)    // "truen"
    } catch {
        print(error)
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search