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.
struct Article: Codable {
var name: String
}
let encoder = JSONEncoder()
let article = Article(name: "jaime")
let encodedArticle = try encoder.encode(article)
// 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)
let decoder = JSONDecoder()
let data = "{\"name\":\"jaime\"}".data(using: .utf8)!
let article = try decoder.decode(Article.self, from: data)
// 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())
let jsonString = article.encode(using: JSONStringEncoder())
// output: "{\"name\":\"jaime\"}"
let dictionary = article.encode(using: JSONSerializationEncoder())
// output: ["name": "jaime"]
let article = try Article.decode(from: "{\"name\":\"jaime\"}",
using: JSONStringDecoder())
let article = try Article.decode(from: ["name": "jaime"],
using: JSONSerializationDecoder())
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
}
}
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)
}
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)
}
let page1 = try container.decode(.page, as: Int.self)
let page2 = try container.decode(.page) as Int
let page3: Int = try container.decode(.page)
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