JSONのObjectに当たるもののclass(struct)を定義するのはわかるのだが、JSONのprimitiveValue(numberとかstringとか)に当たるもののclass(struct)を定義する方法が全然ドキュメントされていないのでメモ。
まとめ
- CustomPrimitiveValueのCodable実装でinit(from decoder)とencode(to)を自分で実装しよう
- singlevaluecontainerを使おう
フルサンプル
PersonがObject.
NameがPrimitiveValue(string互換).
構造
person: {
name: Name
}
です。以下どうぞ
Name.swift
// こいつはjson上Objectではなくstringと同様のprimitiveなvalueとして扱いたい
struct Name {
let string: String
init(_ string: String) {
self.string = string
}
}
extension Name: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.string = try container.decode(String.self)
}
}
extension Name: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(string)
}
}
Person.swift
struct Person {
let name: Name? // ←独自の型且つOptionalだと自動的に動かない 独自の型がCodableである必要がある。 今回はNameをCodableにしたので自動的に動く
}
extension Person: Codable {
enum CodingKeys: String, CodingKey {
case name
}
}
Test.swift
func testEncode() -> String {
let person = Person(name: Name("taro"))
let data = try! JSONEncoder().encode(person)
return String(data: data, encoding: .utf8)!
}
testEncode() // => "{"name":"taro"}"
extension Person: CustomStringConvertible {
var description: String {
return "Person<name: \(name?.description ?? "nil")>"
}
}
extension Name: CustomStringConvertible {
var description: String {
return "Name<\(string.description)>"
}
}
func testDecode() -> Person {
let json = "{\"name\":\"suzuki12\"}"
let data = json.data(using: .utf8)!
return try! JSONDecoder().decode(Person.self, from: data)
}
testDecode() // => Person<name: Name<suzuki>>
蛇足
公式ドキュメント(Apple)でSingleValueContainerが触れられてないのどういうこと。流し読みしたら、primitiveな値にCodable使うべきではないのかなと勘違いするところだった。
独自のEncoderを作る方法はこちらがとてもわかりやすいです
https://stackoverflow.com/questions/45169254/swift-custom-encoder-decoder-to-strings-resource-format