Consider the following JSON: I'm trying to decode the "teams" object.
let jsonString = """
{
"Superheroes":{
"Marvel":"107",
"DC":"106"
},
"teams":{
"106":{
"name":"Marvel",
"Superheroes":{
"890":{
"name":"Batman"
}
}
},
"107":{
"name":"DC",
"Superheroes":{
"891":{
"name":"Wonder Woman"
}
}
}
}
}
"""
I have tried something like this:
struct SuperheroResponse: Decodable {
let teams: [Team]
private enum CodingKeys: String, CodingKey {
case teams = "teams"
}
private struct DynamicCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let teamContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: CodingKeys.teams)
print(teamContainer.allKeys.count)
let tempArray: [Team] = []
for key in teamContainer.allKeys {
let decodedObject = try teamContainer.decode(Team.self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!)
tempArray.append(decodedObject)
}
teams = tempArray
}
}
struct Team: Decodable {
let name: String
}
I thought that first I would get the teams container, map over the keys and go on from there. Problem is teamContainer.allKeys.count is always zero.
Also the following line, results in following error: Cannot convert value of type 'SuperheroResponse.DynamicCodingKeys' to expected argument type 'SuperheroResponse.CodingKeys'
let decodedObject = try teamContainer.decode(Team.self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!)
Finally I decode it as follows:
let jsonData = Data(jsonString.utf8)
let decodedResult = try! JSONDecoder().decode(SuperheroResponse.self, from: jsonData)
dump(decodedResult)
Any help would be appreciated. Ideally I would like something like SuperheroResponse -> [Team],
Team -> name, [Superhero], Superhero -> name
You just have a couple of minor mistakes. You're almost there.
The team container is keyed by DynamicCodingKeys:
let teamContainer = try container.nestedContainer(keyedBy: DynamicCodingKeys.self, // <=
forKey: .teams)
And the Teams can be decoded as using the key you're given:
let decodedObject = try teamContainer.decode(Team.self, forKey: key)
Also, tempArray needs to be var:
var tempArray: [Team] = []
Or replace that loop with a map:
teams = try teamContainer.allKeys.map {
try teamContainer.decode(Team.self, forKey: $0)
}
All together:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let teamContainer = try container.nestedContainer(keyedBy: DynamicCodingKeys.self, forKey: .teams)
teams = try teamContainer.allKeys.map {
try teamContainer.decode(Team.self, forKey: $0)
}
}
Related
I have lots of values in my codable struct. I have URLs coming in as ""(empty string) so I need custom Decoder to convert "" as nil. So I made a propertyWrapper to solve this.
For example, I have values like ["https://google.com", "", "https://google.com"] and I want to make it as [URL?]. This works well with my decoder. It's converted as [URL("https://google.com"), nil, URL("https://google.com")]
However, I found a problem that, when I use init(from decoder: Decoder) throws, I also have to initialize all other values in struct. Is there any way to use just courseImages and use other values in struct as set?
struct CabinetCourse: Codable {
let courseId: String
let title: String
let planStartDate: Date
let planEndDate: Date
let companionTypeCd: String
let courseCategory: String
let planId: String
let nickname: String?
let isCabinet: Bool
let isFavorite: Bool?
let course: String
let score: String
let shareCnt: Int
let favoriteCnt: Int
let cabinetCnt: Int
let placeCount: Int
let createDt: Date
let childPlaceCount: Int
let wheelChairPlaceCount: Int
let elderPlaceCount: Int
#OptionalObject
var courseImages: [URL?]
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let emptyURLS = try values.decode([OptionalObject<URL>].self, forKey: .courseImages)
courseImages = emptyURLS.map { $0.wrappedValue }
// => these are the lines I don't want to write
courseId = try values.decode(String.self, forKey: .courseId)
title = try values.decode(String.self, forKey: .title)
planStartDate = try values.decode(Date.self, forKey: .planStartDate)
planEndDate = try values.decode(Date.self, forKey: .planEndDate)
companionTypeCd = try values.decode(String.self, forKey: .companionTypeCd)
courseCategory = try values.decode(String.self, forKey: .courseCategory)
planId = try values.decode(String.self, forKey: .planId)
nickname = try values.decode(String.self, forKey: .nickname)
isCabinet = try values.decode(Bool.self, forKey: .isCabinet)
isFavorite = try values.decode(Bool.self, forKey: .isCabinet)
course = try values.decode(String.self, forKey: .isCabinet)
score = try values.decode(String.self, forKey: .isCabinet)
}
#propertyWrapper
struct OptionalObject<Base: Decodable>: Decodable {
var wrappedValue: Base?
init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Base.self)
} catch {
wrappedValue = nil
}
}
}
You are misunderstanding property wrappers. They "decorate" the entire property type - [URL?], not just the array element type URL?. [URL?] doesn't match the type of wrappedValue. [URL]? does (Base == [URL]), but that's not what you want.
One way to create a property wrapper that can be applied to an array of optionals is:
#propertyWrapper
struct OptionalArray<Base: Decodable>: Decodable {
var wrappedValue: [Base?]
...
Now Base == URL matches [URL?], and in init, you have to decode a [Base?]:
init(from decoder: Decoder) throws {
var arr = [Base?]()
var container = try decoder.unkeyedContainer()
for _ in 0..<(container.count ?? 0) {
if let element = try? container.decode(Base.self) {
arr.append(element)
} else {
arr.append(nil)
_ = try container.decode(String.self) // advances the decoder to the next position
}
}
wrappedValue = arr
}
Once you have the property wrapper, you don't need the custom decoding code at all. Swift figures it out.
struct CabinetCourse: Decodable {
let courseId: String
let title: String
let planStartDate: Date
let planEndDate: Date
let companionTypeCd: String
let courseCategory: String
let planId: String
let nickname: String?
let isCabinet: Bool
let isFavorite: Bool?
let course: String
let score: String
let shareCnt: Int
let favoriteCnt: Int
let cabinetCnt: Int
let placeCount: Int
let createDt: Date
let childPlaceCount: Int
let wheelChairPlaceCount: Int
let elderPlaceCount: Int
#OptionalArray
var courseImages: [URL?]
}
// That's it!
For the encoding part, it depends on how you want to encode the nils. But either way, the code is very similar to the decoding code.
I was going through couchbase-lite to use it in my next iOS app. I have created a model named Surah for now. Definitely, I will have more model classes later.
Basically I have four questions here.
How do I add _id as my primary key in couchbase-lite?
As I will be having more classes how will I handle those? As I am creating
MutableDocument, How will that differentiate each my classes?
As I can see I have to iterate through each of my items to batch insert, won't that become slow for the large datasets?
How do i convert results from a query with large data to a array of Model Class. (in this case of array of Surah)
class Surah: Decodable {
enum Keys: String, CodingKey {
case _id
case index
case englishName
case englishMeaning
case name
case place
case count
}
var _id = ""
var index = 1
var page = 1
var numberOfAyahs = 1
var englishName = ""
var englishMeaning = ""
var name = ""
var place = ""
var isFavorite = false
var dictionary: [String: Any] {
return ["_id": _id, "index": index, "page": page]
}
required init() {}
required init(_id: String, index: Int, name: String, englishName: String, englishMeaning: String, place: String, count: Int) {
self._id = _id
self.index = index
self.name = name
self.englishName = englishName
self.englishMeaning = englishMeaning
self.place = place
self.numberOfAyahs = count
}
required convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self) // defining our (keyed) container
let _id: String = try container.decode(String.self, forKey: ._id)
let index: Int = try container.decode(Int.self, forKey: .index)
let name: String = try container.decode(String.self, forKey: .name)
let englishName: String = try container.decode(String.self, forKey: .englishName)
let englishMeaning: String = try container.decode(String.self, forKey: .englishMeaning)
let place: String = try container.decode(String.self, forKey: .place)
let count: Int = try container.decode(Int.self, forKey: .count)
self.init(_id: _id, index: index, name: name, englishName: englishName, englishMeaning: englishMeaning, place: place, count: count)
}}
Code for Database Queries
let surahs = try JSONDecoder().decode([Surah].self, from: data!)
DispatchQueue.global(qos: .background).async {
//background code
do {
if let db = App.shared.database {
try db.inBatch {
for item in surahs {
let doc = MutableDocument(data: item.dictionary)
doc.setString("users", forKey: "type")
doc.setValue(Keys._id, forKey: item._id)
// doc.setValue(Keys.englishName, forKey: item.englishName)
try db.saveDocument(doc)
let index = IndexBuilder.valueIndex(items:
ValueIndexItem.expression(Expression.property("_id")), ValueIndexItem.expression(Expression.property("type")))
try db.createIndex(index, withName: "TypeNameIndex")
print("saved user document \(doc.string(forKey: "englishName"))")
}
}
}
} catch let error {
DispatchQueue.main.async {
seal.reject(error)
}
}
DispatchQueue.main.async {
seal.fulfill(surahs)
}
}
Not sure what you mean by "primary key". You can always find a doc
by its id. The name of the field that contains it is Meta.id.
The field's value is mutableDoc.getId(). As you've noticed, you
can also explicitly set the id at creation
Couchbase doesn't store classes, it stores JSON documents. If you
have documents of different types (different internal structures,
analogous to different SQL tables), give them a type field and use
it in your query
Use Database.inBatch()
The same way you would convert any JSON document to a corresponding
class: gson, Jackson, Moshi, etc
Here is the code I am using,
struct CreatePostResponseModel : Codable{
var transcodeId:String?
var id:String = ""
enum TopLevelCodingKeys: String, CodingKey {
case _transcode = "_transcode"
case _transcoder = "_transcoder"
}
enum CodingKeys:String, CodingKey{
case id = "_id"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TopLevelCodingKeys.self)
if let transcodeId = try container.decodeIfPresent(String.self, forKey: ._transcode) {
self.transcodeId = transcodeId
}else if let transcodeId = try container.decodeIfPresent(String.self, forKey: ._transcoder) {
self.transcodeId = transcodeId
}
}
}
Here, transcodeId is decided by either _transcode or _transcoder.
But I want id and rest of the keys (not included here) to be automatically decoded. How can I do it ?
You need to manually parse all the keys once you implement init(from:) in the Codable type.
struct CreatePostResponseModel: Decodable {
var transcodeId: String?
var id: String
enum CodingKeys:String, CodingKey{
case id, transcode, transcoder
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(String.self, forKey: .id) ?? ""
if let transcodeId = try container.decodeIfPresent(String.self, forKey: .transcode) {
self.transcodeId = transcodeId
} else if let transcodeId = try container.decodeIfPresent(String.self, forKey: .transcoder) {
self.transcodeId = transcodeId
}
}
}
In the above code,
In case you only want to decode the JSON, there is no need to use Codable. Using Decodable is enough.
Using multiple enums for CodingKey seems unnecessary here. You can use a single enum CodingKeys.
If the property name and the key name is an exact match, there is no need to explicitly specify the rawValue of that case in enum CodingKeys. So, there is no requirement of "_transcode" and "_transcoder" rawValues in TopLevelCodingKeys.
Apart from all that, you can use keyDecodingStrategy as .convertFromSnakeCase to handle underscore notation (snake case notation), i.e.
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase //here.....
let model = try decoder.decode(CreatePostResponseModel.self, from: data)
print(model)
} catch {
print(error)
}
So, you don't need to explicitly handle all the snake-case keys. It'll be handled by the JSONDecoder on its own.
This can be one of the good solution for you wherever you want you can add multiple keys for one variable:
var transcodeId:String?
public init(from decoder: Decoder) throws {
do {
let container = try decoder.container(keyedBy: CodingKeys.self)
transcodeId = container.getValueFromAvailableKey(codingKeys: [CodingKeys._transcoder,CodingKeys._transcode])
} catch {
print("Error reading config file: \(error.localizedDescription)")
}
}
extension KeyedDecodingContainerProtocol{
func getValueFromAvailableKey(codingKeys:[CodingKey])-> String?{
for key in codingKeys{
for keyPath in self.allKeys{
if key.stringValue == keyPath.stringValue{
do{
return try self.decodeIfPresent(String.self, forKey: keyPath)
} catch {
return nil
}
}
}
}
return nil
}
}
Hope it helps.
The compiler-generated init(from:) is all-or-nothing. You can’t have it decode some keys and “manually” decode others.
One way to use the compiler-generated init(from:) is by giving your struct both of the possible encoded properties, and make transcodeId a computed property:
struct CreatePostResponseModel: Codable {
var transcodeId: String? {
get { _transcode ?? _transcoder }
set { _transcode = newValue; _transcoder = nil }
}
var _transcode: String? = nil
var _transcoder: String? = nil
var id: String = “”
// other properties
}
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.
Let's say I need to transform a date string I received from a web service to a Date object.
Using ObjectMapper, that was easy:
class Example: Mappable {
var date: Date?
required init?(map: Map) { }
func mapping(map: Map) {
date <- (map["date_of_interest"], GenericTransform().dateTransform)
}
}
I just had to implement a tranformer ("GenericTransform" in this case) for date, and pass it as an argument along with the key name to decode.
Now, using Codable:
class Example2: Codable {
var name: String
var age: Int
var date: Date?
enum CodingKeys: String, CodingKey {
case name, age
case date = "date_of_interest"
}
}
To transform a date, in my understanding, I'd have to either:
1) Pass a dateDecodingStrategy to my JSONDecoder, which I don't want to, because I'm trying to keep that part of the code as a generic function.
or
2) Implement an init(from decoder: Decoder) inside Example2, which I also don't want to, because of the boilerplate code I'd have to write to decode all the other properties (which would be automatically generated otherwise):
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
age = try container.decode(Int.self, forKey: .age)
let dateString = try container.decode(String.self, forKey: .date)
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
if let date = formatter.date(from: dateString) {
self.date = date
} else {
//throw error
}
}
My question is: is there an easier way to do it than options 1 and 2 above?
Maybe tweaking the CodingKeys enum somehow?
EDIT:
The problem is not only about dates, actually. In this project that I'm working on, there are many custom transformations being done using TransformOf<ObjectType, JSONType> from ObjectMapper.
For example, a color transformation of a hex code received from a web service into a UIColor is done using this bit of code:
let colorTransform = TransformOf<UIColor, String>(fromJSON: { (value) -> UIColor? in
if let value = value {
return UIColor().hexStringToUIColor(hex: value)
}
return nil
}, toJSON: { _ in
return nil
})
I'm trying to remove ObjectMapper from the project, making these same transformations using Codable, so only using a custom dateDecodingStrategy will not suffice.
How would you guys do it? Implement a custom init(from decoder: Decoder) for every class that has to decode, for example, a color hex code?
Using dateDecodingStrategy in your case (as you only reference a single date format) is very simple…
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(formatter)
We can use custom method for example like decodeAll here. Try in playground.
struct Model: Codable {
var age: Int?
var name: String?
enum CodingKeys: String, CodingKey {
case name
case age
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decodeAll(String.self, forKey: .name)
age = try container.decodeAll(Int.self, forKey: .age)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try? container.encode(name, forKey: .name)
try? container.encode(age, forKey: .age)
}
}
extension KeyedDecodingContainer where K: CodingKey, K: CustomDebugStringConvertible {
func decodeAll<T: Decodable>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T {
if let obj = try? decode(T.self, forKey: key) {
return obj
} else {
if type == String.self {
if let obj = try? decode(Int.self, forKey: key), let val = String(obj) as? T {
return val
} else if let obj = try? decode(Double.self, forKey: key), let val = String(obj) as? T {
return val
}
} else if type == Int.self {
if let obj = try? decode(String.self, forKey: key), let val = Int(obj) as? T {
return val
} else if let obj = try? decode(Double.self, forKey: key), let val = Int(obj) as? T {
return val
}
} else if type == Double.self {
if let obj = try? decode(String.self, forKey: key), let val = Double(obj) as? T {
return val
} else if let obj = try? decode(Int.self, forKey: key), let val = Double(obj) as? T {
return val
}
}
}
throw DecodingError.typeMismatch(T.self, DecodingError.Context(codingPath: codingPath, debugDescription: "Wrong type for: \(key.stringValue)"))
}
}
let json = ##"{ "age": "5", "name": 98 }"##
do {
let obj = try JSONDecoder().decode(Model.self, from: json.data(using: .utf8)!)
print(obj)
} catch {
print(error)
}