jlainog / Codable-Utils

Extensions over Codable

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Codable-Utils

Swift Package Manager GitHub license GitHub tag (latest SemVer)

Motivation

The new Framework Combine introduce a couple of protocols that create a common interface for Encoders and Decoders allowing to chain them.

but those are available only in iOS 13.0+ Wanting to use those in previous versions motivate this set of utils API starting by declaring AnyTopLevelEncoder & AnyTopLevelDecoder and then creating extension for Encodable and Decodable to use them.

Examples

Given

struct Article: Codable {
    var name: String
}

With Encodable

let encoder = JSONEncoder()
let article = Article(name: "jaime")
let encodedArticle = try encoder.encode(article)

With Codable-Utils

// Encode with JSONEncoder by default
let article = Article(name: "jaime")
let encodedArticle = try article.encode()

// Using an specific encoder
let encoder = PropertyListEncoder()
let encodedArticle = try article.encode(using: encoder)

With Decodable

let decoder = JSONDecoder()
let data = "{\"name\":\"jaime\"}".data(using: .utf8)!
let article = try decoder.decode(Article.self, from: data)

With Codable-Utils

// Decode with JSONDecoder by default
let data = "{\"name\":\"jaime\"}".data(using: .utf8)!
let article = try Article.decode(from: data)

// Using an specific decoder
let article = try Article.decode(from: data,
                                 using: JSONDecoder())

Custom Encoders

let jsonString = article.encode(using: JSONStringEncoder())
// output: "{\"name\":\"jaime\"}"
let dictionary = article.encode(using: JSONSerializationEncoder())
// output: ["name": "jaime"]

Custom Decoders

let article = try Article.decode(from: "{\"name\":\"jaime\"}",
                                 using: JSONStringDecoder())
let article = try Article.decode(from: ["name": "jaime"],
                                 using: JSONSerializationDecoder())

Extras

Type Inference can be leverage using the extension added over KeyedDecodingContainer

struct Page: Decodable {
    var index: Int
    var title: String
    var content: String?
    
    enum CodingKeys: String, CodingKey {
        case page, title, content
    }
}

Regular way

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    
    index = try container.decode(Int.self, forKey: .page)
    title = try container.decode(String, forKey: .title)
    content = try container.decodeIfPresent(String.self, forKey: .content)
}

Using the extension

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    
    index = try container.decode(.page)
    title = try container.decode(.title)
    content = try container.decodeIfPresent(.content)
}

3-ways to do the same

let page1 = try container.decode(.page, as: Int.self)
let page2 = try container.decode(.page) as Int
let page3: Int = try container.decode(.page)

Encoding/Decoding Dictionary using enum as a key

struct Photo: Codable {
    enum URLKind: String, CodingKey {
        case raw, full, regular, small, thumb
    }
    enum CodingKeys: String, CodingKey {
        case urls
    }
    
    let urls: [URLKind: String]
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        urls = try container.decodeDictionary(.urls)
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encodeDictionary(urls, forKey: .urls)
    }
}

A default implementation for CustomDebugStringConvertible is added to Encodable types returning a JSON string representation by simple make the Encodable type to conform it.

extension Article: CustomDebugStringConvertible {}
print(article.ddebugDescription)
// output: "{\n  \"name\" : \"jaime\"\n}"
po article
//{
//  "name" : "jaime"
//}

As a plus a simple type inferred way to load data and decode it from the Bundle.

// load as Data
let data = try bundle.load("article.json")
// load and decoded as type
try Bundle.main.load("article.json",
                     using: JSONDecoder()) as Article

About

Extensions over Codable

License:MIT License


Languages

Language:Swift 100.0%