web-dev-qa-db-ja.com

SwiftのEncodableを使用して、カスタムエンコードなしでオプションのプロパティをnullとしてエンコードします

JSONEncoderプロトコルに準拠するstructを使用して、SwiftのEncodableでオプションのフィールドをエンコードしたいと思います。

デフォルト設定では、JSONEncoderencodeIfPresentメソッドを使用します。これは、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}
13
fl034

このようなものを使用して、単一の値をエンコードできます。

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")])
0
highFly

この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)の実装に値する非常にまれなケースだと思います。これはまったく難しいことではありませんが、少し標準的でない動作を明確にするのは少し面倒です。

これは私には公正な妥協のように見えます。

0
Patru