1. ホーム
  2. ios

[解決済み] JSONEncoderでnil値をnullとしてエンコードする

2023-06-12 12:32:22

質問

私はSwift 4の JSONEncoder . を持っているのですが Codable 構造体にオプションのプロパティがあり、このプロパティを null の値として表示させたい。 nil . しかし JSONEncoder はプロパティを破棄し、JSON出力に追加されません。を設定する方法はありますか? JSONEncoder を構成して、キーを保持し、それを null に設定するのでしょうか?

以下のコード・スニペットは {"number":1} を生成しますが、私はむしろこのコードで {"string":null,"number":1} :

struct Foo: Codable {
  var string: String? = nil
  var number: Int = 1
}

let encoder = JSONEncoder()
let data = try! encoder.encode(Foo())
print(String(data: data, encoding: .utf8)!)

どのように解決するのですか?

はい、しかし、あなたは独自の encode(to:) の実装を書く必要があり、自動生成されたものを使うことはできません。

struct Foo: Codable {
    var string: String? = nil
    var number: Int = 1

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(number, forKey: .number)
        try container.encode(string, forKey: .string)
    }
}

オプショナルを直接エンコードすると、探しているようなNULLがエンコードされます。

もしこれがあなたにとって重要なユースケースであるなら、以下のサイトで欠陥を開くことを検討してください。 bugs.swift.org にて、新しい OptionalEncodingStrategy フラグを JSONEncoder に追加し、既存の DateEncodingStrategy などです(なぜこれが今日のSwiftで実際に実装することが不可能である可能性が高いかを以下でご覧ください。しかし、追跡システムに入ることはSwiftが進化する際にまだ役に立ちます)。


編集: 下のPauloの質問に対して、これは一般的な encode<T: Encodable> バージョンにディスパッチします。 Optional に準拠しています。 Encodable . この実装は Codable.swift をこのようにします.

extension Optional : Encodable /* where Wrapped : Encodable */ {
    @_inlineable // FIXME(sil-serialize-all)
    public func encode(to encoder: Encoder) throws {
        assertTypeIsEncodable(Wrapped.self, in: type(of: self))

        var container = encoder.singleValueContainer()
        switch self {
        case .none: try container.encodeNil()
        case .some(let wrapped): try (wrapped as! Encodable).__encode(to: &container)
        }
    }
}

の呼び出しをラップしています。 encodeNil そして、stdlib に Optionals をただの Encodable として扱わせる方が、独自のエンコーダで特別なケースとして扱って encodeNil を呼び出すよりも良いと思います。

もうひとつの明白な疑問は、そもそもなぜこのように動作するのかということです。Optional は Encodable であり、生成された Encodable 適合性はすべてのプロパティをエンコードするので、なぜ "encode all the properties by hand" は異なる動作をするのでしょうか。その答えは、適合性生成器が がOptionalsのための特別なケースを含んでいるからである。 :

// Now need to generate `try container.encode(x, forKey: .x)` for all
// existing properties. Optional properties get `encodeIfPresent`.
...

if (varType->getAnyNominal() == C.getOptionalDecl() ||
    varType->getAnyNominal() == C.getImplicitlyUnwrappedOptionalDecl()) {
  methodName = C.Id_encodeIfPresent;
}

つまり、この動作を変更するには、自動生成されたコンフォーマンスを変更する必要があり、そのためには JSONEncoder (を変更する必要があるということです(これはまた、今日のSwiftで設定可能にするのが本当に難しいということでしょう......)。