Skip to content

Instantly share code, notes, and snippets.

@marcuswu0814
Created September 13, 2021 10:26
Show Gist options
  • Save marcuswu0814/2b0b16e62e25cf25638a657d111b1017 to your computer and use it in GitHub Desktop.
Save marcuswu0814/2b0b16e62e25cf25638a657d111b1017 to your computer and use it in GitHub Desktop.
SwagGen NullCodable Proposal
import UIKit
@propertyWrapper
public struct NullCodable<Value> {
public enum EncodingOption {
case encodeNull, `default`
}
public struct EncodingOptionWrapper {
var encodingOption: EncodingOption
}
enum CodingKeys: String, CodingKey {
case wrappedValue
}
public var wrappedValue: Value?
public var projectedValue: EncodingOptionWrapper = .init(encodingOption: .default)
public init(wrappedValue: Value?) {
self.wrappedValue = wrappedValue
}
}
extension NullCodable: Encodable where Value: 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 Value: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
wrappedValue = try container.decode(Value.self)
}
}
}
extension KeyedEncodingContainer {
public mutating func encodeIfPresent<Value>(
_ value: NullCodable<Value>,
forKey key: KeyedDecodingContainer<K>.Key
) throws where Value : Encodable {
if value.projectedValue.encodingOption == .default {
try encodeIfPresent(value.wrappedValue, forKey: key)
} else {
try encode(value.wrappedValue, forKey: key)
}
}
}
extension KeyedDecodingContainer {
public func decode<Value>(
_ type: NullCodable<Value>.Type,
forKey key: KeyedDecodingContainer<K>.Key
) throws -> NullCodable<Value> where Value: Decodable {
return try decodeIfPresent(NullCodable<Value>.self, forKey: key) ?? NullCodable<Value>(wrappedValue: nil)
}
}
// MARK: - Codable Model
struct MyData: Codable {
let id: String
@NullCodable var optionalValue: String?
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encodeIfPresent(_optionalValue, forKey: .optionalValue)
}
}
// MARK: - Decoding Test
let jsonA = """
{
"id" : "a",
"optionalValue" : null
}
"""
.data(using: .utf8)!
let jsonB = """
{
"id" : "b"
}
"""
.data(using: .utf8)!
let jsonC = """
{
"id" : "a",
"optionalValue" : "Not Empty"
}
"""
.data(using: .utf8)!
let decoder = JSONDecoder()
var objectFromJsonA = try! decoder.decode(MyData.self, from: jsonA)
var objectFromJsonB = try! decoder.decode(MyData.self, from: jsonB)
var objectFromJsonC = try! decoder.decode(MyData.self, from: jsonC)
print(objectFromJsonA.optionalValue == nil)
print(objectFromJsonB.optionalValue == nil)
print(objectFromJsonC.optionalValue != nil)
// MARK: - Encoding Test
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
objectFromJsonA.$optionalValue.encodingOption = .default
objectFromJsonB.$optionalValue.encodingOption = .encodeNull
objectFromJsonC.$optionalValue.encodingOption = .default
let a = try! encoder.encode(objectFromJsonA)
let b = try! encoder.encode(objectFromJsonB)
let c = try! encoder.encode(objectFromJsonC)
print(String(data: a, encoding: .utf8)!)
print(String(data: b, encoding: .utf8)!)
print(String(data: c, encoding: .utf8)!)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment