Last active
June 7, 2023 13:02
-
-
Save lukeredpath/186022efcd7377c7e42329e576cbccd2 to your computer and use it in GitHub Desktop.
A wrapper for raw representable values when decoding them from JSON
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
let jsonPayload = """ | |
{ | |
"category": "animal" | |
} | |
""" | |
struct JsonValue: Codable { | |
enum Category: String, UnknownRawValueRepresenting { | |
case animal, mineral, vegetable, unknownValue | |
} | |
@UnknownRepresentableValueHandling | |
var category: Category | |
} | |
let decoder = JSONDecoder() | |
let value = try decoder.decode(jsonPayload.data(using: .utf8)!, as: JsonValue.self) | |
print(value.$category) // => "animal" | |
print(value.category) // => JsonValue.Category.animal |
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
Copyright 2023 Luke Redpath | |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
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 | |
/// A raw representable value that can provide a fallback for unknown raw values. | |
public protocol UnknownRawValueRepresenting: RawRepresentable { | |
/// The value to use when given an unknown raw value. | |
static var unknownValue: Self { get } | |
} | |
/// A property wrapper that wraps raw values that can be used as the raw value for a raw representable type. | |
/// | |
/// This is useful for codable types that have some raw value (usually a `String`) in the JSON payload that | |
/// you want to decode as some kind of raw representable type, like an enum. Decoding directly to the enum | |
/// can be prone to failure if new raw values are returned from the API that the enum doesn't know about - this | |
/// would cause the decoding for the entire JSON payload to fail. | |
/// | |
/// By storing and decoding the raw value and computing the enum value at runtime, we can be sure that any | |
/// future values will not break JSON decoding. This property wrapper provides access to the original raw value | |
/// through it's projected value if you still need to inspect it, but will always return the `unknownValue` when | |
/// given an unknown raw value. | |
/// | |
/// - Note: this property wrapper implements equatability and hashability based on the original raw value, | |
/// so two different unknown values will not be the same even if they have the same computed wrapped value. | |
@propertyWrapper | |
public struct UnknownRepresentableValueHandling<ValueType: UnknownRawValueRepresenting> { | |
private var _wrappedValue: ValueType | |
public var rawValue: ValueType.RawValue | |
/// Returns the computed wrapped value - this will be the value's unknown value if the raw value is unknown. | |
public var wrappedValue: ValueType { | |
get { _wrappedValue } | |
set { | |
_wrappedValue = newValue | |
rawValue = _wrappedValue.rawValue | |
} | |
} | |
/// Returns the original raw value, even if unknown. | |
public var projectedValue: ValueType.RawValue { | |
get { rawValue } | |
} | |
/// Initializes the property wrapper with an existing wrapped value. | |
public init(wrappedValue: ValueType) { | |
_wrappedValue = wrappedValue | |
rawValue = wrappedValue.rawValue | |
} | |
} | |
extension UnknownRepresentableValueHandling: RawRepresentable { | |
/// Initializes the wrapped value from a raw value, or uses the unknown value if not known. | |
public init(rawValue: ValueType.RawValue) { | |
self.rawValue = rawValue | |
_wrappedValue = .init(rawValue: rawValue) ?? ValueType.unknownValue | |
} | |
} | |
extension UnknownRepresentableValueHandling: Hashable where ValueType.RawValue: Hashable { | |
public func hash(into hasher: inout Hasher) { | |
hasher.combine(rawValue) | |
} | |
} | |
extension UnknownRepresentableValueHandling: Equatable where ValueType.RawValue: Equatable { | |
public static func == (lhs: UnknownRepresentableValueHandling<ValueType>, rhs: UnknownRepresentableValueHandling<ValueType>) -> Bool { | |
lhs.rawValue == rhs.rawValue | |
} | |
} | |
extension UnknownRepresentableValueHandling: Encodable where ValueType.RawValue: Encodable { | |
public func encode(to encoder: Encoder) throws { | |
var container = encoder.singleValueContainer() | |
try container.encode(rawValue) | |
} | |
} | |
extension UnknownRepresentableValueHandling: Decodable where ValueType.RawValue: Decodable { | |
public init(from decoder: Decoder) throws { | |
let container = try decoder.singleValueContainer() | |
rawValue = try container.decode(ValueType.RawValue.self) | |
_wrappedValue = .init(rawValue: rawValue) ?? ValueType.unknownValue | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment