JSONEncoder
プロトコルに準拠するstruct
を使用して、SwiftのEncodable
でオプションのフィールドをエンコードしたいと思います。
デフォルト設定では、JSONEncoder
はencodeIfPresent
メソッドを使用します。これは、nil
である値がJsonから除外されることを意味します。
カスタムencode(to encoder: Encoder)
関数を記述せずに、単一のプロパティに対してこれをオーバーライドするにはどうすればよいですか。すべてのプロパティのエンコーディングを実装する必要があります( この記事 「カスタムエンコーディング」で提案されているように) )?
例:
struct MyStruct: Encodable {
let id: Int
let date: Date?
}
let myStruct = MyStruct(id: 10, date: nil)
let jsonData = try JSONEncoder().encode(myStruct)
print(String(data: jsonData, encoding: .utf8)!) // {"id":10}
このようなものを使用して、単一の値をエンコードできます。
struct CustomBody: Codable {
let method: String
let params: [Param]
enum CodingKeys: String, CodingKey {
case method = "method"
case params = "params"
}
}
enum Param: Codable {
case bool(Bool)
case integer(Int)
case string(String)
case stringArray([String])
case valueNil
case unsignedInteger(UInt)
case optionalString(String?)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Bool.self) {
self = .bool(x)
return
}
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode([String].self) {
self = .stringArray(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(UInt.self) {
self = .unsignedInteger(x)
return
}
throw DecodingError.typeMismatch(Param.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Param"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .bool(let x):
try container.encode(x)
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
case .stringArray(let x):
try container.encode(x)
case .valueNil:
try container.encodeNil()
case .unsignedInteger(let x):
try container.encode(x)
case .optionalString(let x):
x?.isEmpty == true ? try container.encodeNil() : try container.encode(x)
}
}
}
そして使い方はこんな感じ
RequestBody.CustomBody(method: "WSDocMgmt.getDocumentsInContentCategoryBySearchSource",
params: [.string(legacyToken), .string(shelfId), .bool(true), .valueNil, .stringArray(queryFrom(filters: filters ?? [])), .optionalString(sortMethodParameters()), .bool(sortMethodAscending()), .unsignedInteger(segment ?? 0), .unsignedInteger(segmentSize ?? 0), .string("NO_PATRON_STATUS")])
このJSON
をデコードしようとすると、信頼できるJSONDecoder
は、このプレイグラウンドで例示されているものとまったく同じオブジェクトを作成します。
import Cocoa
struct MyStruct: Codable {
let id: Int
let date: Date?
}
let jsonDataWithNull = """
{
"id": 8,
"date":null
}
""".data(using: .utf8)!
let jsonDataWithoutDate = """
{
"id": 8
}
""".data(using: .utf8)!
do {
let withNull = try JSONDecoder().decode(MyStruct.self, from: jsonDataWithNull)
print(withNull)
} catch {
print(error)
}
do {
let withoutDate = try JSONDecoder().decode(MyStruct.self, from: jsonDataWithoutDate)
print(withoutDate)
} catch {
print(error)
}
これは印刷されます
MyStruct(id: 8, date: nil)
MyStruct(id: 8, date: nil)
したがって、「標準」からSwiftの観点から、あなたの区別はほとんど意味がありません。もちろんそれを決定することはできますが、道は厄介で、JSONSerialization
の煉獄を通り抜けます。または[String:Any]
デコードともっと醜いオプション。もちろん、インターフェースで別の言語を提供している場合は意味がありますが、それでもencode(to encoder: Encoder)
の実装に値する非常にまれなケースだと思います。これはまったく難しいことではありませんが、少し標準的でない動作を明確にするのは少し面倒です。
これは私には公正な妥協のように見えます。