我使用的是Swift 4的JSONEncoder
。我有一个带有可选属性的Codable
结构,我希望这个属性在null
值为nil
时在生成的JSON值中显示为JSON值。但是,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)!)
发布于 2020-06-11 03:51:54
下面是一种使用属性包装器的方法(需要Swift v5.1):
@propertyWrapper
struct NullEncodable<T>: Encodable where T: Encodable {
var wrappedValue: T?
init(wrappedValue: T?) {
self.wrappedValue = wrappedValue
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch wrappedValue {
case .some(let value): try container.encode(value)
case .none: try container.encodeNil()
}
}
}
示例用法:
struct Tuplet: Encodable {
let a: String
let b: Int
@NullEncodable var c: String? = nil
}
struct Test: Encodable {
@NullEncodable var name: String? = nil
@NullEncodable var description: String? = nil
@NullEncodable var tuplet: Tuplet? = nil
}
var test = Test()
test.tuplet = Tuplet(a: "whee", b: 42)
test.description = "A test"
let data = try JSONEncoder().encode(test)
print(String(data: data, encoding: .utf8) ?? "")
输出:
{
"name": null,
"description": "A test",
"tuplet": {
"a": "whee",
"b": 42,
"c": null
}
}
发布于 2020-03-21 18:00:24
这是我们在项目中使用的一种方法。希望能有所帮助。
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")])
发布于 2020-03-04 19:28:59
正如@Peterdk所提到的,已经创建了关于此问题的错误报告:
https://bugs.swift.org/browse/SR-9232
如果你想坚持这个特性应该如何在未来的版本中成为官方API的一部分,请随意投票支持它。
而且,正如Johan Nordberg在这篇错误报告中提到的,有一个库FineJson可以处理这个问题,而不必为所有的可编码结构重写每个encode(to:)
实现^
下面的示例展示了我如何使用这个库在应用程序的后端请求的NULL
有效负载中编码JSON值:
import Foundation
import FineJSON
extension URLRequest {
init<T: APIRequest>(apiRequest: T, settings: APISettings) {
// early return in case of main conf failure
guard let finalUrl = URL(string: apiRequest.path, relativeTo: settings.baseURL) else {
fatalError("Bad resourceName: \(apiRequest.path)")
}
// call designated init
self.init(url: finalUrl)
var parametersData: Data? = nil
if let postParams = apiRequest.postParams {
do {
// old code using standard JSONSerializer :/
// parametersData = try JSONSerializer.encode(postParams)
// new code using FineJSON Encoder
let encoder = FineJSONEncoder.init()
// with custom 'optionalEncodingStrategy' ^^
encoder.optionalEncodingStrategy = .explicitNull
parametersData = try encoder.encode(postParams)
// set post params
self.httpBody = parametersData
} catch {
fatalError("Encoding Error: \(error)")
}
}
// set http method
self.httpMethod = apiRequest.httpMethod.rawValue
// set http headers if needed
if let httpHeaders = settings.httpHeaders {
for (key, value) in httpHeaders {
self.setValue(value, forHTTPHeaderField: key)
}
}
}
}
这些是我为了处理这个问题而必须执行的唯一更改。
感谢Omochi的这个伟大的库;)
希望这能帮上忙。
https://stackoverflow.com/questions/47266862
复制相似问题