我想用Swift的JSONEncoder使用符合Encodable协议的struct对一个可选字段进行编码。
默认设置是JSONEncoder使用encodeIfPresent方法,这意味着nil值被排除在Json之外。
如何才能在不编写自定义encode(to encoder: Encoder)函数的情况下为单个属性覆盖它(就像this article在“自定义编码”中建议的那样)?
示例:
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}发布于 2018-11-04 23:54:03
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属性访问可选值
发布于 2018-05-23 04:27:13
如果您尝试解码此JSON,您可信任的JSONDecoder将创建与此游乐场中示例完全相同的对象:
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)因此,从“标准”Swift的角度来看,你的区别没有什么意义。你当然可以决定它,但道路是艰难的,并通过JSONSerialization或[String:Any]解码的炼狱和更多丑陋的选项。当然,如果你用你的接口服务于另一种语言,这可能是有意义的,但我仍然认为这是相当罕见的情况,很容易值得实现encode(to encoder: Encoder),这一点都不难,只是要澄清你的稍微不标准的行为有点乏味。
在我看来,这是一个公平的妥协。
发布于 2020-03-21 17:56:16
您可以使用类似这样的内容对单个值进行编码。
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")])https://stackoverflow.com/questions/47290582
复制相似问题