问题描述
我正在使用Swift 4的 JSONEncoder
。我有一个带有可选属性的 Codable
结构,我希望这个属性显示为 null
值当值 nil
时生成的JSON数据。但是, JSONEncoder
会丢弃该属性,而不会将其添加到JSON输出中。有没有办法配置 JSONEncoder
,以便它保留密钥并在这种情况下将其设置为 null
?
I'm using Swift 4's JSONEncoder
. I have a Codable
struct with an optional property, and I'd like this property to show up as null
value in the produced JSON data when the value is nil
. However, JSONEncoder
discards the property and does not add it to the JSON output. Is there a way to configure JSONEncoder
so that it preserves the key and sets it to null
in this case?
下面的代码片段产生 {number:1}
,但我更喜欢它给我 {string:null,number:1}
:
The code snippet below produces {"number":1}
, but I'd rather like it to give me {"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)!)
推荐答案
是的,但你必须编写自己的编码器;你不能使用默认的。
Yes, but you'll have to write your own encoder; you can't use the default one.
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,就像您正在寻找的那样。
Encoding an optional directly will encode a null, like you're looking for.
如果这是一个重要的用例,你可以考虑在上打开一个缺陷,要求新的 OptionalEncodingStrategy
要在JSONEncoder上添加标志以匹配现有的 DateEncodingStrategy
等(见下文为什么这可能是不可能的)今天实际上在Swift中实现了,但是随着Swift的发展,进入跟踪系统仍然很有用。)
If this is an important use case for you, you may consider opening a defect at bugs.swift.org to ask for a new OptionalEncodingStrategy
flag to be added on JSONEncoder to match the existing DateEncodingStrategy
, etc. (See below why this is likely impossible to actually implement in Swift today, but getting into the tracking system is still useful as Swift evolves.)
编辑:到保罗的问题如下,这将发送到通用的编码< T:En可编码>
版本因为可选
符合 Encodable
。这是在这样:
To Paulo's questions below, this dispatches to the generic encode<T: Encodable>
version because Optional
conforms to Encodable
. This is implemented in Codable.swift this way:
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比在我们自己的编码器中将它们视为特殊情况并调用<$ c更好$ c> encodeNil 我们自己。
This wraps the call to encodeNil
, and I think letting stdlib handle Optionals as just another Encodable is better than treating them as a special case in our own encoder and calling encodeNil
ourselves.
另一个obvi问题是为什么它首先以这种方式工作。由于Optional是Encodable,并且生成的Encodable一致性对所有属性进行编码,为什么手动编码所有属性的工作方式不同?答案是一致性生成器:
Another obvious question is why it works this way in the first place. Since Optional is Encodable, and the generated Encodable conformance encodes all the properties, why does "encode all the properties by hand" work differently? The answer is that the conformance generator includes a special case for 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中可能很难配置....)
This means that changing this behavior would require changing the auto-generated conformance, not JSONEncoder
(which also means it's probably really hard to make configurable in today's Swift....)
这篇关于使用JSONEncoder将nil值编码为null的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!