私はSwift 4のJSONEncoder
を使用しています。オプションのプロパティを持つCodable
構造体があり、値がnull
の場合、このプロパティを生成されたJSONデータの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 で欠陥を開いて、既存と一致するJSONEncoderに追加する新しいOptionalEncodingStrategy
フラグを要求することを検討できます。 DateEncodingStrategy
など(以下を参照してください。実際にSwiftで実際に実装することが不可能である可能性が高いのですが、追跡システムに入ることはSwift進化します。)
編集:以下のPauloの質問に、これはOptional
がEncodable
に準拠しているため、汎用encode<T: 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がOptionalを別のEncodableとして処理できるようにすることは、独自のエンコーダで特殊なケースとして扱い、encodeNil
を呼び出すよりも優れていると思います。
別の明らかな質問は、そもそもなぜこのように機能するのかということです。 OptionalはEncodableであり、生成されたEncodable準拠はすべてのプロパティをエンコードするので、「すべてのプロパティを手動でエンコードする」のはなぜ機能しないのですか?答えは、適合ジェネレーター オプションの特殊なケースを含む :
// 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で設定可能にするのはおそらく非常に難しいことも意味します)。
私は同じ問題に遭遇しました。 JSONEncoderを使用せずに構造体から辞書を作成することで解決しました。これは、比較的普遍的な方法で実行できます。私のコードは次のとおりです。
struct MyStruct: Codable {
let id: String
let regionsID: Int?
let created: Int
let modified: Int
let removed: Int?
enum CodingKeys: String, CodingKey, CaseIterable {
case id = "id"
case regionsID = "regions_id"
case created = "created"
case modified = "modified"
case removed = "removed"
}
var jsonDictionary: [String : Any] {
let mirror = Mirror(reflecting: self)
var dic = [String: Any]()
var counter = 0
for (name, value) in mirror.children {
let key = CodingKeys.allCases[counter]
dic[key.stringValue] = value
counter += 1
}
return dic
}
}
extension Array where Element == MyStruct {
func jsonArray() -> [[String: Any]] {
var array = [[String:Any]]()
for element in self {
array.append(element.jsonDictionary)
}
return array
}
}
CodingKeysなしでこれを行うことができます(サーバー側のテーブル属性名が構造プロパティ名と等しい場合)。その場合は、mirror.childrenの「名前」を使用してください。
CodingKeysが必要な場合は、CaseIterableプロトコルを追加することを忘れないでください。これにより、allCases変数を使用できます。
ネストされた構造体には注意してください。型としてカスタム構造を持つプロパティがある場合は、それも辞書に変換する必要があります。これはforループで実行できます。
MyStruct辞書の配列を作成する場合は、Array拡張機能が必要です。