MoreCodable
MoreCodable expands the possibilities of "Codable".
Installation
Carthage
github "tattn/MoreCodable"
CocoaPods
pod 'MoreCodable'
Feature
DictionaryEncoder / DictionaryDecoder
struct User: Codable {
let id: Int
let name: String
}
let encoder = DictionaryEncoder()
let user = User(id: 123, name: "tattn")
let dictionary: [String: Any] = try! encoder.encode(user) // => {"id": 123, "name": "tattn"}
let decoder = DictionaryDecoder()
let user = try decoder.decode(User.self, from: dictionary)
URLQueryItemsEncoder / URLQueryItemsDecoder
struct Parameter: Codable {
let query: String
let offset: Int
let limit: Int
}
let parameter = Parameter(query: "ねこ", offset: 10, limit: 20)
let encoder = URLQueryItemsEncoder()
let params: [URLQueryItem] = try! encoder.encode(parameter)
var components = URLComponents(string: "https://example.com")
components?.queryItems = params
components?.url // https://example.com?query=%E3%81%AD%E3%81%93&offset=10&limit=20
let decoder = URLQueryItemsDecoder()
let parameter = try decoder.decode(Parameter.self, from: params)
ObjectMerger
struct APIResponse: Encodable {
let id: Int
let title: String
let foo: String
}
struct APIResponse2: Encodable {
let tags: [String]
}
struct Model: Decodable {
let id: Int
let title: String
let tags: [String]
}
let response = APIResponse(id: 0, title: "Awesome article", foo: "bar")
let response2 = APIResponse2(tags: ["swift", "ios", "macos"])
let model = try ObjectMerger().merge(Model.self, response, response2)
// success
XCTAssertEqual(model.id, response.id)
XCTAssertEqual(model.title, response.title)
XCTAssertEqual(model.tags, response2.tags)
RuleBasedCodingKey
struct User: Codable {
let userId: String
let name: String
enum CodingKeys: String, RuleBasedCodingKey {
case userId
case name
func codingKeyRule(key: String) -> String {
return key.uppercased() // custom rule
}
}
}
let json = """
{"USERID": "abc", "NAME": "tattn"}
""".data(using: .utf8)!
let user = try! JSONDecoder().decode(User.self, from: json) // => User(userId: "abc", name: "tattn")
SnakeCaseCodingKey
struct User: Codable {
let userId: String
let name: String
enum CodingKeys: String, SnakeCaseCodingKey {
case userId
case name
}
}
let json = """
{"user_id": "abc", "name": "tattn"}
""".data(using: .utf8)!
let user = try! JSONDecoder().decode(User.self, from: json) // ok
UpperCamelCaseCodingKey
struct User: Codable {
let userId: String
let name: String
enum CodingKeys: String, UpperCamelCaseCodingKey {
case userId
case name
}
}
let json = """
{"UserId": "abc", "Name": "tattn"}
""".data(using: .utf8)!
let user = try! JSONDecoder().decode(User.self, from: json) // ok
Failable
let json = """
[
{"name": "Taro", "age": 20},
{"name": "Hanako"}
]
""".data(using: .utf8)! // Hanako has no "age"
struct User: Codable {
let name: String
let age: Int
}
let users = try! JSONDecoder().decode([Failable<User>].self,
from: json)
// success
XCTAssertEqual(users[0].value?.name, "Taro")
XCTAssertEqual(users[0].value?.age, 20)
XCTAssertNil(users[1].value)
StringTo
let json = """
{
"int": "100",
"articleId": "abc"
}
""".data(using: .utf8)!
struct Root: Codable {
let int: StringTo<Int>
let articleId: StringTo<ArticleId>
struct ArticleId: LosslessStringConvertible, Codable {
var description: String
init?(_ description: String) {
self.description = description
}
}
}
let root = try! JSONDecoder().decode(Root.self, from: json)
// success
XCTAssertEqual(root.int.value, 100)
XCTAssertEqual(root.articleId.value.description, "abc")
MultiDateFormat
let json = """
{
"date": "2019.05.27",
"dateTime": "2019-05-27T17:26:59+0000",
"timestamp": 1558978068,
"timestampMilliseconds": 1558978141863,
"custom": "1558978068"
}
""".data(using: .utf8)!
struct Document: Codable {
var date: Date
var dateTime: Date
var timestamp: Date
var timestampMilliseconds: Date
var custom: Date
}
extension Document: MultiDateFormat {
static var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.timeZone = TimeZone(identifier: "UTC")
formatter.dateFormat = "yyyy.MM.dd"
return formatter
}()
static func dateFormat(for codingKey: CodingKey) -> DateFormat? {
switch codingKey {
case CodingKeys.date: return .formatted(dateFormatter)
case CodingKeys.dateTime: return .iso8601
case CodingKeys.timestamp: return .secondsSince1970
case CodingKeys.timestampMilliseconds: return .millisecondsSince1970
case CodingKeys.custom: return .custom({ (date, encoder) in
var container = encoder.singleValueContainer()
try container.encode(String(date.timeIntervalSince1970))
}, { (decoder) -> Date in
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
let timeInterval = TimeInterval(string)!
return Date(timeIntervalSince1970: timeInterval)
})
default: return nil
}
}
}
let decoded = try! MoreJSONDecoder().decode(Document.self, from: json)
let encoded = try! MoreJSONEncoder().encode(document)
DictionaryCachableEncoder
struct User: Codable, Hashable { // conform to Hashable
let id: Int
let name: String
}
let encoder = DictionaryCachableEncoder()
let user = User(id: 123, name: "tattn")
let dictionary: [String: Any] = try! encoder.encode(user) // => {"id": 123, "name": "tattn"}
try! encoder.encode(user) // use the previous encoded result for the second time
ToDo
- XMLDecoder/XMLEncoder
- CSVDecoder/CSVEncoder
Related project
DataConvertible
https://github.com/tattn/DataConvertible
Contributing
- Fork it!
- Create your feature branch:
git checkout -b my-new-feature
- Commit your changes:
git commit -am 'Add some feature'
- Push to the branch:
git push origin my-new-feature
- Submit a pull request :D
Support this project
Donating to help me continue working on this project.
License
MoreCodable is released under the MIT license. See LICENSE for details.