1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
/// Property wrapper allowing per-property customization of how the value is
/// encoded/decoded when using Codable.
///
/// CustomCodable is generic over a `CustomCoder: CustomCodableWrapper`, which
/// wraps the underlying value, and provides the specific Codable implementation.
/// Since each instance of CustomCodable provides its own CustomCoder wrapper,
/// properties of the same type can provide different Codable implementations
/// within the same container.
///
/// Example: change the encoding of a property `foo` in the following struct to
/// do its encoding through a String instead of the normal Codable implementation.
///
/// ```
/// struct MyStruct: Codable {
/// @CustomCodable<SillyIntCoding> var foo: Int
/// }
///
/// struct SillyIntCoding: CustomCodableWrapper {
/// init(from decoder: Decoder) throws {
/// wrappedValue = try Int(decoder.singleValueContainer().decoder(String.self))!
/// }
/// func encode(to encoder: Encoder) throws {
/// try encoder.singleValueContainer().encode("\(wrappedValue)")
/// }
/// var wrappedValue: Int { get }
/// init(wrappedValue: WrappedValue) { self.wrappedValue = wrappedValue }
/// }
/// ```
@propertyWrapper
public struct CustomCodable<CustomCoder: CustomCodableWrapper> {
public typealias CustomCoder = CustomCoder
/// The underlying value.
public var wrappedValue: CustomCoder.WrappedValue
public init(wrappedValue: CustomCoder.WrappedValue) {
self.wrappedValue = wrappedValue
}
}
extension CustomCodable: Sendable where CustomCoder.WrappedValue: Sendable {}
extension CustomCodable: Codable {
public init(from decoder: Decoder) throws {
self.wrappedValue = try CustomCoder(from: decoder).wrappedValue
}
public func encode(to encoder: Encoder) throws {
try CustomCoder(wrappedValue: self.wrappedValue).encode(to: encoder)
}
}
extension CustomCodable: Equatable where CustomCoder.WrappedValue: Equatable {}
extension CustomCodable: Hashable where CustomCoder.WrappedValue: Hashable {}
/// Wrapper type providing a Codable implementation for use with `CustomCodable`.
public protocol CustomCodableWrapper: Codable {
/// The type of the underlying value being wrapped.
associatedtype WrappedValue
/// The underlying value.
var wrappedValue: WrappedValue { get }
/// Create a wrapper from an underlying value.
init(wrappedValue: WrappedValue)
}
extension Optional: CustomCodableWrapper where Wrapped: CustomCodableWrapper {
public var wrappedValue: Wrapped.WrappedValue? { self?.wrappedValue }
public init(wrappedValue: Wrapped.WrappedValue?) {
self = wrappedValue.flatMap { Wrapped.init(wrappedValue: $0) }
}
}
// The following extensions allow us to encode `CustomCodable<Optional<T>>`
// using `encodeIfPresent` (and `decodeIfPresent`) in synthesized `Codable`
// conformances. Without these, we would encode `nil` using `encodeNil` instead
// of skipping the key.
extension KeyedDecodingContainer {
public func decode<T: CustomCodableWrapper>(
_ type: CustomCodable<Optional<T>>.Type,
forKey key: Key
) throws -> CustomCodable<Optional<T>> {
CustomCodable<Optional<T>>(wrappedValue: try decodeIfPresent(T.self, forKey: key)?.wrappedValue)
}
}
extension KeyedEncodingContainer {
public mutating func encode<T: CustomCodableWrapper>(
_ value: CustomCodable<Optional<T>>,
forKey key: Key
) throws {
try encodeIfPresent(
value.wrappedValue.map {
type(of: value).CustomCoder(wrappedValue: $0)
},
forKey: key
)
}
}
|