Created
September 13, 2021 10:26
-
-
Save marcuswu0814/2b0b16e62e25cf25638a657d111b1017 to your computer and use it in GitHub Desktop.
SwagGen NullCodable Proposal
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 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