Last active
October 19, 2023 12:04
-
-
Save mredig/f6d9efb196a25d857fe04a28357551a6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
/// Makes an Encodable and Decodable properties encode to `null` instead of omitting the value altogether. | |
@propertyWrapper | |
public struct NullCodable<T> { | |
public var wrappedValue: T? | |
public init(wrappedValue: T?){ | |
self.wrappedValue = wrappedValue | |
} | |
} | |
extension NullCodable: Encodable where T: Encodable { | |
public 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() | |
} | |
} | |
} | |
extension NullCodable: Decodable where T: Decodable { | |
public init(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
self.wrappedValue = try? container.decode(T.self) | |
} | |
} | |
struct WithNullCodable: Codable { | |
let value: String | |
@NullCodable var value2: String? | |
} | |
struct WithoutNullCodable: Codable { | |
let value: String | |
let value2: String? | |
} | |
let withA = WithNullCodable(value: "blah", value2: "bleh") | |
let withB = WithNullCodable(value: "blah", value2: nil) | |
let withoutA = WithoutNullCodable(value: "blah", value2: "bleh") | |
let withoutB = WithoutNullCodable(value: "blah", value2: nil) | |
func jsonString<Item: Encodable>(from item: Item) -> String { | |
let data = try! JSONEncoder().encode(item) | |
return String(data: data, encoding: .utf8)! | |
} | |
func object<Item: Decodable>(from string: String) -> Item { | |
let decoder = JSONDecoder() | |
let data = string.data(using: .utf8)! | |
return try! decoder.decode(Item.self, from: data) | |
} | |
let withAStr = jsonString(from: withA) | |
let withBStr = jsonString(from: withB) | |
print(withAStr) // {"value":"blah","value2":"bleh"} | |
print(withBStr) // {"value":"blah","value2":null} | |
let withoutAStr = jsonString(from: withoutA) | |
let withoutBStr = jsonString(from: withoutB) | |
print(withoutAStr) // {"value":"blah","value2":"bleh"} | |
print(withoutBStr) // {"value":"blah"} | |
let withADecoded: WithNullCodable = object(from: withAStr) | |
let withBDecoded: WithNullCodable = object(from: withBStr) | |
print(withADecoded) | |
print(withBDecoded) | |
let withoutADecoded: WithoutNullCodable = object(from: withoutAStr) | |
let withoutBDecoded: WithoutNullCodable = object(from: withoutBStr) | |
print(withoutADecoded) | |
print(withoutBDecoded) |
Thanks for quick answer! What do you mean by flattening the wrapper of the content
?
Without the singleValueContainer()
it might be trying to decode a value embedded within another container, like an array or dictionary.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It's been a while since I wrote this, so I cannot remember for sure why I put that in there, but I believe I wouldn't have included it unless it wasn't working without.
I suspect it might have something to do with flattening the wrapper of the content within NullCodable? I'd suggest experimenting. If you figure it out, I'll happily update with a comment explaining why it's there (as I should have initially!)