I want to encode an optional field with Swift's JSONEncoder
using a struct
that conforms to the Encodable
protocol.
The default setting is that JSONEncoder
uses the encodeIfPresent
method, which means that values that are nil
are excluded from the Json.
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" )?
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}
A null value (no string) is treated as nil by default so the decoding is supposed to succeed if the property is optional. By the way: You can omit the CodingKeys. If the name of the properties are the same as the keys you don't need explicit CodingsKeys .
The Codable protocol in Swift is really a union of two protocols: Encodable and Decodable . These two protocols are used to indicate whether a certain struct, enum, or class, can be encoded into JSON data, or materialized from JSON data.
A type that can be used as a key for encoding and decoding.
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:
{
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With