我想使用符合JSONEncoder
协议的struct
对Swift Encodable
的可选字段进行编码。
默认设置是JSONEncoder
使用encodeIfPresent
方法,这意味着从{J}中排除nil
的值。
如何在不编写自定义encode(to encoder: Encoder)
函数的情况下为单个属性覆盖此属性,我必须在其中实现所有属性的编码(例如“自定义编码”下的this article建议)?< / p>
示例:
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}
答案 0 :(得分:0)
如果您尝试对此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)
}
这将打印
MyStruct(id: 8, date: nil)
MyStruct(id: 8, date: nil)
所以从“标准”斯威夫特的角度来看,你的区别几乎没有意义。你当然可以确定它,但路径是棘手的,并导致JSONSerialization
或[String:Any]
解码的炼狱以及更多丑陋的选择。当然,如果您使用可能有意义的界面提供另一种语言,但我仍然认为这是一个相当罕见的案例,很容易实现encode(to encoder: Encoder)
并不难,只是有点单调乏味来澄清你的略微不符合标准的行为。
这对我来说是一个公平的妥协。
答案 1 :(得分:0)
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()
这将在执行main之后为您提供输出:
{
age = 36;
gender = "<null>";
name = William;
surname = Lowson;
}
{
gender = "<null>";
}
因此,您可以看到我们对性别进行了编码,因为它在字典中为空。您将获得的唯一限制是,您必须通过value
属性访问可选值
答案 2 :(得分:0)
您可以使用类似的方式对单个值进行编码。
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")])