[英]Use Swift's Encodable to encode optional properties as null without custom encoding
I want to encode an optional field with Swift's JSONEncoder
using a struct
that conforms to the Encodable
protocol.我想使用符合
Encodable
协议的struct
使用 Swift 的JSONEncoder
对可选字段进行编码。
The default setting is that JSONEncoder
uses the encodeIfPresent
method, which means that values that are nil
are excluded from the Json.默认设置是
JSONEncoder
使用encodeIfPresent
方法,这意味着从 Json 中排除nil
值。
How can I override this for a single property without writing my custom encode(to encoder: Encoder)
function, in which I have to implement the encoding for all properties (like this article suggests under "Custom Encoding" )?如何在不编写我的自定义
encode(to encoder: Encoder)
函数的情况下为单个属性覆盖它,我必须在其中实现所有属性的编码(如本文在“自定义编码”下建议)?
Example:例子:
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}
import Foundation
enum EncodableOptional<Wrapped>: ExpressibleByNilLiteral {
case none
case some(Wrapped)
init(nilLiteral: ()) {
self = .none
}
}
extension EncodableOptional: Encodable where Wrapped: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .none:
try container.encodeNil()
case .some(let wrapped):
try wrapped.encode(to: encoder)
}
}
}
extension EncodableOptional{
var value: Optional<Wrapped> {
get {
switch self {
case .none:
return .none
case .some(let v):
return .some(v)
}
}
set {
switch newValue {
case .none:
self = .none
case .some(let v):
self = .some(v)
}
}
}
}
struct User: Encodable {
var name: String
var surname: String
var age: Int?
var gender: EncodableOptional<String>
}
func main() {
var user = User(name: "William", surname: "Lowson", age: 36, gender: nil)
user.gender.value = "male"
user.gender.value = nil
print(user.gender.value ?? "")
let jsonEncoder = JSONEncoder()
let data = try! jsonEncoder.encode(user)
let json = try! JSONSerialization.jsonObject(with: data, options: [])
print(json)
let dict: [String: Any?] = [
"gender": nil
]
let d = try! JSONSerialization.data(withJSONObject: dict, options: [.prettyPrinted])
let j = try! JSONSerialization.jsonObject(with: d, options: [])
print(j)
}
main()
This will give you output after executing main:这将在执行 main 后为您提供输出:
{
age = 36;
gender = "<null>";
name = William;
surname = Lowson;
}
{
gender = "<null>";
}
So, you can see that we encoded gender as it'll be null in dictionary.因此,您可以看到我们对性别进行了编码,因为它在字典中将为空。 The only limitation you'll get is that you'll have to access optional value via
value
property您将获得的唯一限制是您必须通过
value
属性访问可选值
If you try to decode this JSON
your trusty JSONDecoder
will create exactly the same object as exemplified in this Playground:如果您尝试解码此
JSON
您可信赖的JSONDecoder
将创建与此 Playground 中示例的完全相同的对象:
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)
}
This will print这将打印
MyStruct(id: 8, date: nil)
MyStruct(id: 8, date: nil)
so from a "standard" Swift point of view your distinction makes very little sense.因此,从“标准”Swift 的角度来看,您的区分意义不大。 You can of course determine it, but the path is thorny and leads through the purgatory of
JSONSerialization
or [String:Any]
decoding and a lot more ugly optionals.您当然可以确定它,但路径是棘手的,并且需要通过
JSONSerialization
或[String:Any]
解码的炼狱以及更多丑陋的选项。 Of course if you are serving another language with your interface that might make sense, but still I consider it a rather rare case which easily merits the implementation of encode(to encoder: Encoder)
which is not hard at all, just a little tedious to clarify your slightly non-standard behaviour.当然,如果您使用可能有意义的界面提供另一种语言,但我仍然认为这是一种相当罕见的情况,很容易实现
encode(to encoder: Encoder)
这一点都不难,只是有点乏味澄清你稍微不标准的行为。
This looks like a fair compromise to me.这对我来说似乎是一个公平的妥协。
You can use something like this to encode single values.您可以使用这样的东西来编码单个值。
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)
}
}
}
And the usage is like this用法是这样的
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")])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.