Skip to content

Instantly share code, notes, and snippets.

@lnfnunes
Created May 14, 2021 04:33
Show Gist options
  • Save lnfnunes/f9af5d188b806f9f2538ef43ebe8b70c to your computer and use it in GitHub Desktop.
Save lnfnunes/f9af5d188b806f9f2538ef43ebe8b70c to your computer and use it in GitHub Desktop.
Generic property-wrapper to provide default values for non-optional properties when they are not present or have a nil value.
//
// Decodable.swift
//
// Copyright © 2021 Leandro Nunes Fantinatto. All rights reserved.
// Ref: https://github.com/gonzalezreal/DefaultCodable
// Ref: https://github.com/JohnSundell/Codextended
//
import Foundation
/// Generic property-wrapper to provide default values for non-optional properties when they are not present or have a nil value.
/// Use-case examples:
/// - @DecodableDefault.EmptyString var title: String
/// - @DecodableDefault.False var isFeaturedEnabled: Bool
/// - @DecodableDefault.True var isFeatureEnabled: Bool
/// - @DecodableDefault.EmptyList var data: [SomeModel]
/// - @DecodableDefault.EmptyMap var dict: [String : Any]
///
enum DecodableDefault {}
protocol DecodableDefaultSource {
associatedtype Value: Decodable
static var defaultValue: Value { get }
}
extension DecodableDefault {
@propertyWrapper
struct Wrapper<Source: DecodableDefaultSource> {
typealias Value = Source.Value
var wrappedValue = Source.defaultValue
}
typealias Source = DecodableDefaultSource
typealias List = Decodable & ExpressibleByArrayLiteral
typealias Map = Decodable & ExpressibleByDictionaryLiteral
// Convenience aliases to reference the provided `enum Sources` as specialized versions of our property wrapper type.
typealias True = Wrapper<Sources.True>
typealias False = Wrapper<Sources.False>
typealias EmptyString = Wrapper<Sources.EmptyString>
typealias EmptyList<T: List> = Wrapper<Sources.EmptyList<T>>
typealias EmptyMap<T: Map> = Wrapper<Sources.EmptyMap<T>>
typealias Zero = Wrapper<Sources.Zero>
typealias One = Wrapper<Sources.One>
enum Sources {
enum True: Source {
static var defaultValue: Bool { true }
}
enum False: Source {
static var defaultValue: Bool { false }
}
enum EmptyString: Source {
static var defaultValue: String { "" }
}
enum EmptyList<T: List>: Source {
static var defaultValue: T { [] }
}
enum EmptyMap<T: Map>: Source {
static var defaultValue: T { [:] }
}
enum Zero: Source {
static var defaultValue: Int { 0 }
}
enum One: Source {
static var defaultValue: Int { 1 }
}
}
}
extension DecodableDefault.Wrapper: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Value.self)
}
}
extension DecodableDefault.Wrapper: Equatable where Value: Equatable {}
extension DecodableDefault.Wrapper: Hashable where Value: Hashable {}
extension DecodableDefault.Wrapper: Encodable where Value: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
extension KeyedDecodingContainer {
func decode<T>(_ type: DecodableDefault.Wrapper<T>.Type, forKey key: Key) throws -> DecodableDefault.Wrapper<T> {
try decodeIfPresent(type, forKey: key) ?? .init()
}
}
//
// Decodable.swift
//
// Created by Leandro Nunes Fantinatto on 13/05/21.
// Copyright © 2021 Leandro Nunes Fantinatto. All rights reserved.
// Ref: https://github.com/gonzalezreal/DefaultCodable
// Ref: https://github.com/JohnSundell/Codextended
//
import Foundation
/// Generic property-wrapper to provide default values for non-optional properties when they are not present or have a nil value.
/// Use-case examples:
/// - @DecodableDefault<EmptyString> var description: String
/// - @DecodableDefault<True|False> var isFoo: Bool
/// - @DecodableDefault<Zero|One> var floatingPoint: Int
/// - @DecodableDefault<ZeroDouble> var floatingPoint: Double
/// - @DecodableDefault<EmptyDictionary> var entities: [String: String]
///
@propertyWrapper
public struct DecodableDefault<Provider: DefaultValueProvider>: Codable {
public var wrappedValue: Provider.Value
public init() {
wrappedValue = Provider.default
}
public init(wrappedValue: Provider.Value) {
self.wrappedValue = wrappedValue
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
wrappedValue = Provider.default
} else {
wrappedValue = try container.decode(Provider.Value.self)
}
}
}
extension DecodableDefault: Equatable where Provider.Value: Equatable {}
extension KeyedDecodingContainer {
func decode<P>(_: DecodableDefault<P>.Type, forKey key: Key) throws -> DecodableDefault<P> {
if let value = try decodeIfPresent(DecodableDefault<P>.self, forKey: key) {
return value
} else {
return DecodableDefault()
}
}
}
extension KeyedEncodingContainer {
mutating func encode<P>(_ value: DecodableDefault<P>, forKey key: Key) throws {
guard value.wrappedValue != P.default else { return }
try encode(value.wrappedValue, forKey: key)
}
}
public protocol DefaultValueProvider {
associatedtype Value: Equatable & Codable
static var `default`: Value { get }
}
// MARK: - Default Value Providers
public enum False: DefaultValueProvider {
public static let `default` = false
}
public enum True: DefaultValueProvider {
public static let `default` = true
}
public enum EmptyString: DefaultValueProvider {
public static let `default` = ""
}
public enum Empty<A>: DefaultValueProvider where A: Codable, A: Equatable, A: RangeReplaceableCollection {
public static var `default`: A { A() }
}
public enum EmptyDictionary<K, V>: DefaultValueProvider where K: Hashable & Codable, V: Equatable & Codable {
public static var `default`: [K: V] { Dictionary() }
}
public enum FirstCase<A>: DefaultValueProvider where A: Codable, A: Equatable, A: CaseIterable {
public static var `default`: A { A.allCases.first! }
}
public enum Zero: DefaultValueProvider {
public static let `default` = 0
}
public enum One: DefaultValueProvider {
public static let `default` = 1
}
public enum ZeroDouble: DefaultValueProvider {
public static let `default`: Double = 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment