I am trying to parse an array of heterogeneous objects using Codable. These objects are also unkeyed as well. I should note that I have the container structure correct, because it DOES loop through and print "it is type1" at all correct times as seen below. I just can't figure out how to access the actual object. Here is my code:
var data: [Any]
public init(from decoder: Decoder) throws {
var container = try! decoder.container(keyedBy: CodingKeys.self).nestedUnkeyedContainer(forKey: .data)
while !container.isAtEnd {
var itemContainer = try container.nestedContainer(keyedBy: CodingKeys.self)
let itemType = try itemContainer.decode(String.self, forKey: .type)
switch itemType {
case "type1":
print("it is type1")
// this does not compile, but is what I need
//let objectOfItem1 = try itemContainer.decode(Type1.self)
// this compiles, but doesn't work because there is no key with these objects
//let objectOfItem1 = try itemContainer.decode(Type1, forKey: .type)
default:
print("test:: it is the default")
}
}
}
private enum CodingKeys: String, CodingKey {
case data
case type
}
And here is the JSON I am trying to decode (many properties committed for clarity):
"contents" : {
"data" : [
{
"type" : "type1",
"id" : "6a406cdd7a9cace5"
},
{
"type" : "type2",
"id" : "ljhdgsouilghoipsu"
}
]
}
How can I correctly get my individual Type1 objects out of this structure?
I think the easy way to get around the heterogenous data is to use an enum as an interim type to wrap your various Item types (which all need to Codable):
To allow myself to test this I've changed your json slightly to give me more heterogenous data for testing. I've used:
let json = """
{
"contents": {
"data": [
{
"type": "type1",
"id": "6a406cdd7a9cace5"
},
{
"type": "type2",
"dbl": 1.01
},
{
"type": "type3",
"int": 5
}
]
}
}
and then created the three final types represented by this json
struct Item1: Codable {
let type: String
let id: String
}
struct Item2: Codable {
let type: String
let dbl: Double
}
struct Item3: Codable {
let type: String
let int: Int
}
To allow decoding the multiple types in a type-safe way (as required by Codable) you need to use a single type that can represent (or wrap) the possible options. An enum with associated values works nicely for this
enum Interim {
case type1 (Item1)
case type2 (Item2)
case type3 (Item3)
case unknown //to handle unexpected json structures
}
So far, so good, but then it gets slightly more complicated when it comes to creating the Interim from the JSON. It will need a CodingKey enum which represents all the possible keys for all the Item# types, and then it will need to decode the JSON linking all these keys to their respective types and data:
extension Interim: Decodable {
private enum InterimKeys: String, CodingKey {
case type
case id
case dbl
case int
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: InterimKeys.self)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "type1":
let id = try container.decode(String.self, forKey: .id)
let item = Item1(type: type, id: id)
self = .type1(item)
case "type2":
let dbl = try container.decode(Double.self, forKey: .dbl)
let item = Item2(type: type, dbl: dbl)
self = .type2(item)
case "type3":
let int = try container.decode(Int.self, forKey: .int)
let item = Item3(type: type, int: int)
self = .type3(item)
default: self = .unknown
}
}
}
This provides the mechanism for decoding the heterogenous components, now we just need to deal with the higher-level keys. As we have a Decodable Interim type this is straightforward:
struct DataArray: Decodable {
var data: [Interim]
}
struct Contents: Decodable {
var contents: DataArray
}
This now means the json can be decoded like this...
let data = Data(json.utf8)
let decoder = JSONDecoder()
do {
let contents = try decoder.decode(Contents.self, from: data)
print(contents)
} catch {
print("Failed to decode JSON")
print(error.localizedDescription)
}
This successfully decodes the data into a nested structure where the major component is the array of Interim types with their associated Item# objects. The above produces the following output, showing these nested types:
Contents(contents: testbed.DataArray(data: [testbed.Interim.type1(testbed.Item1(type: "type1", id: "6a406cdd7a9cace5")), testbed.Interim.type2(testbed.Item2(type: "type2", dbl: 1.01)), testbed.Interim.type3(testbed.Item3(type: "type3", int: 5))]))
I think there should be an even safer way to do this with Type Erasure to provide a more extensible solution, but I've not got my head around that fully yet.
I think you need use this structure:
struct A: Codable {
let contents: B?
enum CodingKeys: String, CodingKey {
case contents
}
struct B: Codable {
let data: [C]?
enum CodingKeys: String, CodingKey {
case data
}
struct C: Codable {
let type : String?
let id : String?
}
}
}
extension A {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let contents = try container.decodeIfPresent(B.self, forKey: .contents)
self.init(contents: contents)
}
}
I'd like to add to flanker's answer an improvement for cleaner approach to avoid having all possible keys to be stored under Interim's CodingKey. Here is an updated Interim
enum Interim: Decodable {
case item1(Item1)
case item2(Item2)
case item3(Item3)
case unknown
init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: Key.self)
// Fallback for any unsupported types
guard let type = try? typeContainer.decode(ItemType.self, forKey: .type) else {
self = .unknown
return
}
// Let corresponding Decodable Item to be initialized from the same decoder.
switch type {
case .type1: self = .item1(try .init(from: decoder))
case .type2: self = .item2(try .init(from: decoder))
case .type3: self = .item3(try .init(from: decoder))
}
}
/// These are values for Item.type
private enum ItemType: String, Decodable {
case type1
case type2
case type3
}
private enum Key: CodingKey {
case type
}
}
Related
I am loading a users notifications, there are different notification types (like, comment, follow) and I need to decode each based on their "type" property but I am new to taking this approach and have spent all day trying to figure out how to do this...
Right now I keep getting the decoding error in my NotificationsEnum and I think its because I don't know how to access the type property inside each object returned in my array.
I tried using this SO question & answer Decode JSON from the nested container and check its type dynamically for typecasting in swift but it is with classes and I am using structs.
How can I decode each of these objects based on their type property?? I feel like I need to figure out how to implement nestedUnkeyedContainer and nestedContainer somehow but I am having a hard time getting this to work.
Also, after getting the decoding to work, how can I put all of these objects in the same array? Would it be a generics since I would switch on their type to route the notifications to different notification views...??
THANK YOU
Different JSON activity I need to decode depending on type
[
{
_id: 61dc9bde82d0715,
type: 'like',
username: 'testuser1',
likeId: 61dcb2d0712
},
{
_id: 61dc9bde82d0715,
type: 'follow',
username: 'testuser2',
followId: 61dc9bd2d0712
},
{
_id: 61dc9b2d0715,
type: 'comment',
username: 'testuser3',
commentId: 61dc9bb2d0712
}
]
PROFILE/PARENT DATA MODEL
struct ProfileData: Decodable {
**otherProperties**
var notifications: [NotificationsEnum]
// ^ Is this correct type? Was thinking I might need some kind of generic array??
}
// HOW CAN I IMPLEMENT nestedUnkeyedContainer and nestedContainer here???
enum NotificationsEnum: Decodable {
enum DecodingError: Error {
case wrongJSON
}
case like(LikeActivity)
case comment(CommentActivity)
case follow(FollowActivity)
enum CodingKeys: String, CodingKey {
case like = "like"
case comment = "comment"
case follow = "follow"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
switch container.allKeys.first {
case .like:
let value = try container.decode(LikeActivity.self, forKey: .like)
self = .like(value)
case .comment:
let value = try container.decode(CommentActivity.self, forKey: .comment)
self = .comment(value)
case .follow:
let value = try container.decode(FollowActivity.self, forKey: .follow)
self = .follow(value)
case .none:
throw DecodingError.wrongJSON
}
}
}
MODELS
struct LikeActivity: Codable {
let id: String
let type: String
let username: String
let likeId: String
}
struct CommentActivity: Codable {
let id: String
let type: String
let username: String
let commentId: String
}
struct FollowActivity: Codable {
let id: String
let type: String
let username: String
let followId: String
}
What you want to do is to first decode type then use a switch over the decoded value and init the corresponding type using the same decoder object.
You can also shorten the CodingKeys enum because of this since we are only using one key.
enum CodingKeys: String, CodingKey {
case type
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "like":
self = try .like(LikeActivity(from: decoder))
case "comment":
self = try .comment(CommentActivity(from: decoder))
case "follow":
self = try .follow(FollowActivity(from: decoder))
default:
throw DecodingError.wrongJSON
}
}
You could also create an enum for the type values
enum ActivityType: String {
case like, comment, follow
}
Then the switch in the init(from:) becomes
switch ActivityType(rawValue: type) {
case .like:
self = try .like(LikeActivity(from: decoder))
case .comment:
self = try .comment(CommentActivity(from: decoder))
case .follow:
self = try .follow(FollowActivity(from: decoder))
default:
throw DecodingError.wrongJSON
}
I have JSON structured like this:
{
"data": [
{
"value":["val1", "val2", "val3"]
},
{
"value":"val"
}
]
}
So how would I parse the "value" field here? Sometimes it is an array of strings and sometimes it is just a single string. What type would I assign to it?
Using SwiftyJSON you can check if the value of the value property is a simple string or an array of strings like this:
let jsonString = """
{
"data": [
{
"value":["val1", "val2", "val3"]
},
{
"value":"val"
}
]
}
"""
let json = JSON(parseJSON: jsonString)
json["data"].array?.forEach { dataItem in
if let dataArray = dataItem["value"].array {
print("array: \(dataArray)")
print("array first item: \(dataArray.first?.string ?? "nil")")
} else if let dataString = dataItem["value"].string {
print("string: \(dataString)")
}
}
Somehow you need to abstract the dictionary values in data array. Then, while accessing the objects you can type check of them, or you do the type check while decoding.
Here is my solution for this problem using native JSONDecoder. The important point is you need to provide decoder strict types of your objects. Any, AnyObject class do not conform Decoder protocol, since they do not have typed-container to decode.
enum B: Decodable {
private enum CodingKeys: CodingKey {
case value
}
enum BDecodingError: Error {
case decoding(String)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let val = try? container.decodeIfPresent(Array<String>.self, forKey: .value) {
self = .array(val)
return
}
if let val = try? container.decodeIfPresent(String.self, forKey: .value) {
self = .string(val)
return
}
throw BDecodingError.decoding("B Decoding Error")
}
case array(Array<String>)
case string(String)
}
B enum is responsible for keeping the objects in dictionaries. You can specify the possible types here and check if decoding to that type is successful.
struct A: Decodable {
let data: [B]
}
A struct simply holds the objects of your dictionary within data array.
I have the following object
struct Properties: Decodable {
var id: String?
var value: String?
var color: String?
}
In the first request to server I get the following response
{
"id":"1",
"color":"red"
}
And after another request I get
{
"id":"1", // the id of the object props is meant for
"props":{
"value":"my value" // I can get any property here
}
}
After the two requests I should have the object with all properties set
By now I decode the second request as following
struct SetAttr: Decodable {
let id: String
let props: [String : Any]
enum SetAttrCodingKeys: String, CodingKey {
case id
case props
}
init(from decoder: Decoder) throws {
let container = try! decoder.container(keyedBy: SetAttrCodingKeys.self)
props = try! container.decode([String : Any].self, forKey: .props)
id = try! container.decode(String.self, forKey: .id)
}
}
But I do not know how to parse props dictionary and set the properties on the first object. I am willing to use a decoding library, but I did not find any that can do this
EDIT:
This is how I tried to set the properties from dictionary, but the solution is not scalable
var myObject: Properties
properties = setAttr.props // [String:Any]
let keys = properties.keys
keys.forEach { key in
if let value = properties[key] {
switch key {
case "value":
myObject.value = value as? String
case "color":
myObject.color = value as? String
default:
break
}
}
}
Just use JSONSerialization which parses whatever you throw at it into arrays and dictionaries. That frees you from all the problems you have with strangely formatted JSON.
For example, the second request will be parsed as a dictionary with two keys "id" and "props", and "props" has a value which is again a dictionary with one key "value" and a value "my value".
And please stop using try! That will cause your app to crash instantly if any input is not expected. Unexpected inputs should be handled, not lead to a crash.
There are various way to do this, but one possible way could be something like this:
struct SecAttr: Decodable {
let id: String
var props: Properties?
private enum CodingKeys: String, CodingKey {
case id
case props
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
if let props = try container.decodeIfPresent(Properties.self, forKey: .props) {
self.props = props
} else {
// decode Properties from the same object
self.props = try Properties(from: decoder)
}
}
}
struct Properties: Decodable {
var value: String?
var color: String?
mutable update(from props: Properties) {
value = props.value ?? value
color = color.value ?? color
}
}
Now you can decode your original object and after getting updated properties, just update them on the original.
I have this json:
{ "stuff": [
{
"type":"car",
"object":{
"a":66,
"b":66,
"c":66 }},
{
"type":"house",
"object":{
"d":66,
"e":66,
"f":66 }},
{
"type":"car",
"object":{
"a":66,
"b":66,
"c":66 }}
]}
As you can see for "car" and "house" there are different "object" structs, but both under the tag "object".
It would be ideal if one ended up with something like
struct StuffItem: Decodable {
let type: TheType
let car: Car
let house: House
}
Is there some Codable, swifty, way to handle this?
The swiftiest way in my opinion is an enum with associated types
This is valid JSON
let jsonString = """
{ "stuff": [
{
"type":"car",
"object":{
"a":66,
"b":66,
"c":66
}
},{
"type":"house",
"object":{
"d":66,
"e":66,
"f":66
}
},{
"type":"car",
"object":{
"a":66,
"b":66,
"c":66
}
}
]}
"""
These are the structs
struct Root : Decodable {
let stuff : [Object]
}
enum Type : String, Decodable { case car, house }
struct Car : Decodable {
let a, b, c : Int
}
struct House : Decodable {
let d, e, f : Int
}
enum Object : Decodable {
case house(House), car(Car)
private enum CodingKeys : String, CodingKey { case type, object }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(Type.self, forKey: .type)
switch type {
case .car:
let carData = try container.decode(Car.self, forKey: .object)
self = .car(carData)
case .house:
let houseData = try container.decode(House.self, forKey: .object)
self = .house(houseData)
}
}
}
And the code to decode the JSON
do {
let result = try JSONDecoder().decode(Root.self, from: Data(jsonString.utf8))
let objects = result.stuff
for object in objects {
switch object {
case .car(let car): print(car)
case .house(let house): print(house)
}
}
} catch {
print(error)
}
You can handle multiple cases by using the enum just define your type, Giving code will help you to parse the JSON by using Struct modal with enum.
// MARK: - Welcome
struct Welcome: Codable {
let stuff: [Stuff]
}
// MARK: - Stuff
struct Stuff: Codable {
let type: String
let object: Object
}
// MARK: - Object
struct Object: Codable {
let a, b, c, d: Int?
let e, f: Int?
}
enum Type: String {
case car
case house
}
func fetchResponse() {
do {
let jsonString = "your json string"
let data = Data(jsonString.utf8)
let result = try JSONDecoder().decode(Welcome.self, from: data)
let objects = result.stuff
let carObjects = objects.filter{$0.type == Type.car.rawValue}
print("Its car array: \(carObjects)")// if you need filters car object then use this
let houseObjects = objects.filter{$0.type == Type.house.rawValue}// if you need filters house object then use this
print("Its house array: \(houseObjects)")
// or you check in loop also
objects.forEach { (stuff) in
switch stuff.type {
case Type.car.rawValue:
print("Its car object")
case Type.house.rawValue:
print("Its house object")
default:
print("Also you can set your one case in `default`")
break
}
}
} catch {
print(error.localizedDescription)
}
}
Another explantion:
Since there are not many explanations of this around, here's another example of what #vadian has explained:
1. In Swift, use enum instead of struct to achieve 'flexible type'.
2. You then need parsers for the 'item', and for the 'type'.
2. Parse, and then just switch to decode, and set the correct type.
So for the JSON above, you'd have
struct YourFeed: Decodable {
let stuff: [Item]
}
Each item can be a Car or a House.
struct Car: Decodable { ... }
struct House: Decodable { ... }
So those are easy.
Now for Item. It can be more than one type.
In Swift, use enum instead of struct to achieve 'flexible type'.
// in Swift, an "enum" is basically a "struct" which can have a flexible type,
// so here we have enum Item rather than struct Item:
enum Item: Decodable {
// so this thing, Item, can be one of these two types:
case car(Car)
case house(House)
Next, simply mirror that in a raw enum which will be used for parsing the "type" field. (You can call it anything, I've just called it "Parse".)
// the relevant key strings for parsing the "type" field:
private enum Parse: String, Decodable {
case car
case house
}
Next, look at the original JSON up top. Each "item" has two fields, "type" and "object". Here they are in a raw enum. (Again you can call it anything, I've just called it "Keys" here.)
// we're decoding an item, what are the top-level tags in item?
private enum Keys: String, CodingKey {
// so, these are just the two fields in item from the json
case type
case object
}
Have an enum to parse the 'item' level, and an enum to parse the 'type'.
Finally, write the initializer for "Item". Simply decode the both the top level and the "type" ...
init(from decoder: Decoder) throws {
// parse the top level
let c = try decoder.container(keyedBy: Keys.self)
// and parse the 'type' field
let t = try c.decode(Parse.self, forKey: .type)
... and you're done. Decode the data (using the relevant class), and set the "Item" enum object to the appropriate type.
Parse those, and then just switch to decode / set the enum.
// we're done, so depending on which of the types it is,
// decode (using the relevant decoder), and become the relevant type:
switch t {
case .car:
let d = try c.decode(Car.self, forKey: .object)
self = .car(d)
case .house:
let d = try c.decode(House.self, forKey: .object)
self = .house(d)
}
}
}
Here's the whole thing in one go:
enum Item: Decodable {
case car(Car)
case house(House)
// the relevant key strings for parsing the 'type' field:
private enum Parse: String, Decodable {
case car
case house
}
// the top-level tags in 'item':
private enum Keys: String, CodingKey {
case type
case object
}
init(from decoder: Decoder) throws {
// parse the top level
let c = try decoder.container(keyedBy: Keys.self)
// parse the 'type' field
let t = try c.decode(Parse.self, forKey: .type)
// we're done, switch to
// decode (using the relevant decoder), and become the relevant type:
switch t {
case .car:
let d = try c.decode(Car.self, forKey: .object)
self = .car(d)
case .house:
let d = try c.decode(House.self, forKey: .object)
self = .house(d)
}
}
}
enum PostType: Decodable {
init(from decoder: Decoder) throws {
// What do i put here?
}
case Image
enum CodingKeys: String, CodingKey {
case image
}
}
What do i put to complete this?
Also, lets say i changed the case to this:
case image(value: Int)
How do I make this conform to Decodable?
Here is my full code (which does not work)
let jsonData = """
{
"count": 4
}
""".data(using: .utf8)!
do {
let decoder = JSONDecoder()
let response = try decoder.decode(PostType.self, from: jsonData)
print(response)
} catch {
print(error)
}
}
}
enum PostType: Int, Codable {
case count = 4
}
Also, how will it handle an enum like this?
enum PostType: Decodable {
case count(number: Int)
}
It's pretty easy, just use String or Int raw values which are implicitly assigned.
enum PostType: Int, Codable {
case image, blob
}
image is encoded to 0 and blob to 1
Or
enum PostType: String, Codable {
case image, blob
}
image is encoded to "image" and blob to "blob"
This is a simple example how to use it:
enum PostType : Int, Codable {
case count = 4
}
struct Post : Codable {
var type : PostType
}
let jsonString = "{\"type\": 4}"
let jsonData = Data(jsonString.utf8)
do {
let decoded = try JSONDecoder().decode(Post.self, from: jsonData)
print("decoded:", decoded.type)
} catch {
print(error)
}
Update
In iOS 13.3+ and macOS 15.1+ it's allowed to en-/decode fragments – single JSON values which are not wrapped in a collection type
let jsonString = "4"
let jsonData = Data(jsonString.utf8)
do {
let decoded = try JSONDecoder().decode(PostType.self, from: jsonData)
print("decoded:", decoded) // -> decoded: count
} catch {
print(error)
}
In Swift 5.5+ it's even possible to en-/decode enums with associated values without any extra code. The values are mapped to a dictionary and a parameter label must be specified for each associated value
enum Rotation: Codable {
case zAxis(angle: Double, speed: Int)
}
let jsonString = #"{"zAxis":{"angle":90,"speed":5}}"#
let jsonData = Data(jsonString.utf8)
do {
let decoded = try JSONDecoder().decode(Rotation.self, from: jsonData)
print("decoded:", decoded)
} catch {
print(error)
}
How to make enums with associated types conform to Codable
This answer is similar to #Howard Lovatt's but avoids creating a PostTypeCodableForm struct and instead uses the KeyedEncodingContainer type provided by Apple as a property on Encoder and Decoder, which reduces boilerplate.
enum PostType: Codable {
case count(number: Int)
case title(String)
}
extension PostType {
private enum CodingKeys: String, CodingKey {
case count
case title
}
enum PostTypeCodingError: Error {
case decoding(String)
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? values.decode(Int.self, forKey: .count) {
self = .count(number: value)
return
}
if let value = try? values.decode(String.self, forKey: .title) {
self = .title(value)
return
}
throw PostTypeCodingError.decoding("Whoops! \(dump(values))")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .count(let number):
try container.encode(number, forKey: .count)
case .title(let value):
try container.encode(value, forKey: .title)
}
}
}
This code works for me on Xcode 9b3.
import Foundation // Needed for JSONEncoder/JSONDecoder
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let decoder = JSONDecoder()
let count = PostType.count(number: 42)
let countData = try encoder.encode(count)
let countJSON = String.init(data: countData, encoding: .utf8)!
print(countJSON)
// {
// "count" : 42
// }
let decodedCount = try decoder.decode(PostType.self, from: countData)
let title = PostType.title("Hello, World!")
let titleData = try encoder.encode(title)
let titleJSON = String.init(data: titleData, encoding: .utf8)!
print(titleJSON)
// {
// "title": "Hello, World!"
// }
let decodedTitle = try decoder.decode(PostType.self, from: titleData)
Swift would throw a .dataCorrupted error if it encounters unknown enum value. If your data is coming from a server, it can send you an unknown enum value at any time (bug server side, new type added in an API version and you want the previous versions of your app to handle the case gracefully, etc), you'd better be prepared, and code "defensive style" to safely decode your enums.
Here is an example on how to do it, with or without associated value
enum MediaType: Decodable {
case audio
case multipleChoice
case other
// case other(String) -> we could also parametrise the enum like that
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(String.self)
switch label {
case "AUDIO": self = .audio
case "MULTIPLE_CHOICES": self = .multipleChoice
default: self = .other
// default: self = .other(label)
}
}
}
And how to use it in a enclosing struct:
struct Question {
[...]
let type: MediaType
enum CodingKeys: String, CodingKey {
[...]
case type = "type"
}
extension Question: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
[...]
type = try container.decode(MediaType.self, forKey: .type)
}
}
To extend on #Toka's answer, you may too add a raw representable value to the enum, and use the default optional constructor to build the enum without a switch:
enum MediaType: String, Decodable {
case audio = "AUDIO"
case multipleChoice = "MULTIPLE_CHOICES"
case other
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(String.self)
self = MediaType(rawValue: label) ?? .other
}
}
It may be extended using a custom protocol that allows to refactor the constructor:
protocol EnumDecodable: RawRepresentable, Decodable {
static var defaultDecoderValue: Self { get }
}
extension EnumDecodable where RawValue: Decodable {
init(from decoder: Decoder) throws {
let value = try decoder.singleValueContainer().decode(RawValue.self)
self = Self(rawValue: value) ?? Self.defaultDecoderValue
}
}
enum MediaType: String, EnumDecodable {
static let defaultDecoderValue: MediaType = .other
case audio = "AUDIO"
case multipleChoices = "MULTIPLE_CHOICES"
case other
}
It can also be easily extended for throwing an error if an invalid enum value was specified, rather than defaulting on a value. Gist with this change is available here: https://gist.github.com/stephanecopin/4283175fabf6f0cdaf87fef2a00c8128.
The code was compiled and tested using Swift 4.1/Xcode 9.3.
A variant of #proxpero's response that is terser would be to formulate the decoder as:
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
guard let key = values.allKeys.first else { throw err("No valid keys in: \(values)") }
func dec<T: Decodable>() throws -> T { return try values.decode(T.self, forKey: key) }
switch key {
case .count: self = try .count(dec())
case .title: self = try .title(dec())
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .count(let x): try container.encode(x, forKey: .count)
case .title(let x): try container.encode(x, forKey: .title)
}
}
This permits the compiler to exhaustively verify the cases, and also doesn't suppress the error message for the case where the encoded value doesn't match the key's expected value.
Actually the answers above are really great, but they are missing some details for what many people need in a continuously developed client/server project. We develop an app while our backend continually evolves over time, which means some enum cases will change that evolution. So we need an enum decoding strategy that is able to decode arrays of enums that contain unknown cases. Otherwise decoding the object that contains the array simply fails.
What I did is quite simple:
enum Direction: String, Decodable {
case north, south, east, west
}
struct DirectionList {
let directions: [Direction]
}
extension DirectionList: Decodable {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var directions: [Direction] = []
while !container.isAtEnd {
// Here we just decode the string from the JSON which always works as long as the array element is a string
let rawValue = try container.decode(String.self)
guard let direction = Direction(rawValue: rawValue) else {
// Unknown enum value found - ignore, print error to console or log error to analytics service so you'll always know that there are apps out which cannot decode enum cases!
continue
}
// Add all known enum cases to the list of directions
directions.append(direction)
}
self.directions = directions
}
}
Bonus: Hide implementation > Make it a Collection
To hide implementation detail is always a good idea. For this you'll need just a little bit more code. The trick is to conform DirectionsList to Collection and make your internal list array private:
struct DirectionList {
typealias ArrayType = [Direction]
private let directions: ArrayType
}
extension DirectionList: Collection {
typealias Index = ArrayType.Index
typealias Element = ArrayType.Element
// The upper and lower bounds of the collection, used in iterations
var startIndex: Index { return directions.startIndex }
var endIndex: Index { return directions.endIndex }
// Required subscript, based on a dictionary index
subscript(index: Index) -> Element {
get { return directions[index] }
}
// Method that returns the next index when iterating
func index(after i: Index) -> Index {
return directions.index(after: i)
}
}
You can read more about conforming to custom collections in this blog post by John Sundell: https://medium.com/#johnsundell/creating-custom-collections-in-swift-a344e25d0bb0
You can do what you want, but it is a bit involved :(
import Foundation
enum PostType: Codable {
case count(number: Int)
case comment(text: String)
init(from decoder: Decoder) throws {
self = try PostTypeCodableForm(from: decoder).enumForm()
}
func encode(to encoder: Encoder) throws {
try PostTypeCodableForm(self).encode(to: encoder)
}
}
struct PostTypeCodableForm: Codable {
// All fields must be optional!
var countNumber: Int?
var commentText: String?
init(_ enumForm: PostType) {
switch enumForm {
case .count(let number):
countNumber = number
case .comment(let text):
commentText = text
}
}
func enumForm() throws -> PostType {
if let number = countNumber {
guard commentText == nil else {
throw DecodeError.moreThanOneEnumCase
}
return .count(number: number)
}
if let text = commentText {
guard countNumber == nil else {
throw DecodeError.moreThanOneEnumCase
}
return .comment(text: text)
}
throw DecodeError.noRecognizedContent
}
enum DecodeError: Error {
case noRecognizedContent
case moreThanOneEnumCase
}
}
let test = PostType.count(number: 3)
let data = try JSONEncoder().encode(test)
let string = String(data: data, encoding: .utf8)!
print(string) // {"countNumber":3}
let result = try JSONDecoder().decode(PostType.self, from: data)
print(result) // count(3)
Features
Simple use. One line in Decodable instance: line eg let enum: DecodableEnum<AnyEnum>
Is decoded with standard mapping mechanism: JSONDecoder().decode(Model.self, from: data)
covered case of receiving unknown data (for example, mapping a Decodable object will not fail if you receive unexpected data)
handle/deliver mapping or decoding errors
Details
Xcode 12.0.1 (12A7300)
Swift 5.3
Solution
import Foundation
enum DecodableEnum<Enum: RawRepresentable> where Enum.RawValue == String {
case value(Enum)
case error(DecodingError)
var value: Enum? {
switch self {
case .value(let value): return value
case .error: return nil
}
}
var error: DecodingError? {
switch self {
case .value: return nil
case .error(let error): return error
}
}
enum DecodingError: Error {
case notDefined(rawValue: String)
case decoding(error: Error)
}
}
extension DecodableEnum: Decodable {
init(from decoder: Decoder) throws {
do {
let rawValue = try decoder.singleValueContainer().decode(String.self)
guard let layout = Enum(rawValue: rawValue) else {
self = .error(.notDefined(rawValue: rawValue))
return
}
self = .value(layout)
} catch let err {
self = .error(.decoding(error: err))
}
}
}
Usage sample
enum SimpleEnum: String, Codable {
case a, b, c, d
}
struct Model: Decodable {
let num: Int
let str: String
let enum1: DecodableEnum<SimpleEnum>
let enum2: DecodableEnum<SimpleEnum>
let enum3: DecodableEnum<SimpleEnum>
let enum4: DecodableEnum<SimpleEnum>?
}
let dictionary: [String : Any] = ["num": 1, "str": "blablabla", "enum1": "b", "enum2": "_", "enum3": 1]
let data = try! JSONSerialization.data(withJSONObject: dictionary)
let object = try JSONDecoder().decode(Model.self, from: data)
print("1. \(object.enum1.value)")
print("2. \(object.enum2.error)")
print("3. \(object.enum3.error)")
print("4. \(object.enum4)")
A lot of good approaches here, but I have not seen one discussing enums with more than one value, although it can be deduced from examples - maybe someone can find a use for this one:
import Foundation
enum Tup {
case frist(String, next: Int)
case second(Int, former: String)
enum TupType: String, Codable {
case first
case second
}
enum CodingKeys: String, CodingKey {
case type
case first
case firstNext
case second
case secondFormer
}
}
extension Tup: Codable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let type = try values.decode(TupType.self, forKey: .type)
switch type {
case .first:
let str = try values.decode(String.self, forKey: .first)
let next = try values.decode(Int.self, forKey: .firstNext)
self = .frist(str, next: next)
case .second:
let int = try values.decode(Int.self, forKey: .second)
let former = try values.decode(String.self, forKey: .secondFormer)
self = .second(int, former: former)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .frist(let str, next: let next):
try container.encode(TupType.first, forKey: .type)
try container.encode(str, forKey: .first)
try container.encode(next, forKey: .firstNext)
case .second(let int, former: let former):
try container.encode(TupType.second, forKey: .type)
try container.encode(int, forKey: .second)
try container.encode(former, forKey: .secondFormer)
}
}
}
let example1 = Tup.frist("123", next: 90)
do {
let encoded = try JSONEncoder().encode(example1)
print(encoded)
let decoded = try JSONDecoder().decode(Tup.self, from: encoded)
print("decoded 1 = \(decoded)")
}
catch {
print("errpr = \(error.localizedDescription)")
}
let example2 = Tup.second(10, former: "dantheman")
do {
let encoded = try JSONEncoder().encode(example2)
print(encoded)
let decoded = try JSONDecoder().decode(Tup.self, from: encoded)
print("decoded 2 = \(decoded)")
}
catch {
print("errpr = \(error.localizedDescription)")
}
Here is a simple example of how to make an enum decodable in Swift.
Sample JSON:
[
{
"title": "1904",
"artist": "The Tallest Man on Earth",
"year": "2012",
"type": "hindi"
},
{
"title": "#40",
"artist": "Dave Matthews",
"year": "1999",
"type": "english"
},
{
"title": "40oz to Freedom",
"artist": "Sublime",
"year": "1996",
"type": "english"
},
{
"title": "#41",
"artist": "Dave Matthews",
"year": "1996",
"type": "punjabi"
}
]
Model struct:
struct Song: Codable {
public enum SongType: String, Codable {
case hindi = "hindi"
case english = "english"
case punjabi = "punjabi"
case tamil = "tamil"
case none = "none"
}
let title: String
let artist: String
let year: String
let type: SongType?
}
Now, you can parse the JSON file and parse the data into an array of songs like below:
func decodeJSON() {
do {
// creating path from main bundle and get data object from the path
if let bundlePath = Bundle.main.path(forResource: "sample", ofType: "json"),
let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) {
// decoding an array of songs
let songs = try JSONDecoder().decode([Song].self, from: jsonData)
// printing the type of song
songs.forEach { song in
print("Song type: \(song.type?.rawValue ?? "")")
}
}
} catch {
print(error)
}
}
Comment below in case of any queries.