Set struct var in a nested enum if fail to decode - swift

I have a simple struct that I need to set a flag on enum decode failure. Not sure now to do that.
struct TestStruct: Codable {
let testEnum: TestEnum
let failDecode: Bool
}
extension TestStruct {
enum TestEnum: String, Codable {
case val1 = "VAL1"
case val2 = "VAL2"
public init(from decoder: Decoder) throws {
let testState = try decoder.singleValueContainer().decode(String.self)
switch testState{
case "VAL1": self = .val1
case "VAL2": self = .val2
default: TestStruct.failDecode = true // fail compile here
}
}
}
}
How do I set failDecode flag on decode failure? Thxs

You need a custom init(from:) initializer for TestStruct. You do not need a custom init(from:) initializer for TestEnum.
struct TestStruct {
let testEnum: TestEnum
let failDecode: Bool
}
extension TestStruct {
enum TestEnum: String, Codable {
case val1 = "VAL1"
case val2 = "VAL2"
}
}
extension TestStruct: Decodable {
public init(from decoder: Decoder) throws {
do {
testEnum = try TestEnum(from: decoder)
failDecode = false
} catch {
failDecode = true
// You must initialize testEnum to some valid value,
// even though you couldn't decode it!
testEnum = .val1
}
}
}
If you want Codable conformance, not just Decodable conformance, then you also need to provide a matching encode(to:) method. Here's a simple implementation:
extension TestStruct: Encodable {
func encode(to encoder: Encoder) throws {
try testEnum.encode(to: encoder)
}
}
(Note that Codable is an alias for Decodable & Encodable, so providing separate Decodable and Encodable conformances is the same as providing a single Codable conformance.)
I ignored the failDecode flag in that implementation of encode(to:), but maybe you want to encode an illegal value if failDecode is set. This version encodes nil if failDecode is true:
extension TestStruct: Encodable {
func encode(to encoder: Encoder) throws {
if failDecode {
try Optional<TestEnum>.none.encode(to: encoder)
} else {
try testEnum.encode(to: encoder)
}
}
}

Related

How swift implement the default Decodable for struct?

struct Person: Decodable {
let firstName: String
}
var data = """
{"firstName": "Fai"}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let parsed = try decoder.decode(Person.self, from: data)
JSONDecoder will decode the data, which is comfirmed to Decodable protocol.
So I want to know how swift implement this. But I can not get any idea in the source code: https://github.com/apple/swift/blob/main/stdlib/public/core/Codable.swift
The Decodable protocol only need to implement an init(from decoder: Decoder) function.
If I am going to do it, I will make an extension for struct:
extension struct: Decodable {
init(from decoder: Decoder) {...}
}
But when I delete the Decodable on my example, the compiler give errors:
Instance method 'decode(_:from:)' requires that 'Person' conform to 'Decodable'
So this is not the swift way to implement this. How's swift way? And where's the source code?
You can see what the compiler writes for you using -print-ast:
echo 'struct Person: Decodable {
let firstName: String
}' | swiftc -print-ast -
This will output most of the auto-generated code (it should include all of the Codable conformances, but there are a few other kinds of auto-generated code that won't include their implementation):
internal struct Person : Decodable {
internal let firstName: String
private enum CodingKeys : CodingKey {
case firstName
#_implements(Equatable, ==(_:_:)) fileprivate static func __derived_enum_equals(_ a: Person.CodingKeys, _ b: Person.CodingKeys) -> Bool {
private var index_a: Int
switch a {
case .firstName:
index_a = 0
}
private var index_b: Int
switch b {
case .firstName:
index_b = 0
}
return index_a == index_b
}
fileprivate func hash(into hasher: inout Hasher) {
private var discriminator: Int
switch self {
case .firstName:
discriminator = 0
}
hasher.combine(discriminator)
}
private init?(stringValue: String) {
switch stringValue {
case "firstName":
self = Person.CodingKeys.firstName
default:
return nil
}
}
private init?(intValue: Int) {
return nil
}
fileprivate var hashValue: Int {
get {
return _hashValue(for: self)
}
}
fileprivate var intValue: Int? {
get {
return nil
}
}
fileprivate var stringValue: String {
get {
switch self {
case .firstName:
return "firstName"
}
}
}
}
internal init(firstName: String)
internal init(from decoder: Decoder) throws {
#_hasInitialValue private let container: KeyedDecodingContainer<Person.CodingKeys> = try decoder.container(keyedBy: Person.CodingKeys.self)
self.firstName = try container.decode(String.self, forKey: Person.CodingKeys.firstName)
}
}
For the full implementation details, see DerivedConformanceCodable.cpp. Probably of most interest to your question is deriveBodyDecodable_init.

Swift struct with custom encoder and decoder cannot conform to 'Encodable'

[Edited to provide a minimal reproducible example ]
This is the complete struct without non relevant vars and functions
InstrumentSet.swift
import Foundation
struct InstrumentsSet: Identifiable, Codable {
private enum CodingKeys: String, CodingKey {
case name = "setName"
case tracks = "instrumentsConfig"
}
var id: String { name }
var name: String
var tracks: [Track]
}
Track.swift
import Foundation
extension InstrumentsSet {
struct Track: Identifiable, Encodable {
private enum TrackKeys: String, CodingKey {
case id = "trackId"
case effects
}
let id: String
var effects: [Effect]?
}
}
extension InstrumentsSet.Track: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TrackKeys.self)
id = try container.decode(String.self, forKey: .id)
effects = try container.decodeIfPresent([Effect].self, forKey: .effects)
}
}
Effect.swift
import Foundation
import AudioKit
import SoundpipeAudioKit
extension InstrumentsSet.Track {
enum Effect: Decodable {
private enum EffectKeys: String, CodingKey {
case effectType = "effectName"
case cutoffFrequency
case resonance
}
case lowPassFilter(LowPassFilterEffect)
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: EffectKeys.self)
let effectType = try container.decode(Effect.EffectType.self, forKey: .effectType)
switch effectType {
case .lowPassFilter:
let cutOffFrequency = try container.decode(ValueAndRange.self, forKey: .cutoffFrequency)
let resonance = try container.decode(ValueAndRange.self, forKey: .resonance)
self = .lowPassFilter(LowPassFilterEffect(cutOffFrequency: cutOffFrequency, resonance: resonance))
default:
fatalError("Not implemented!")
}
}
}
}
extension InstrumentsSet.Track.Effect {
enum EffectType: String, Decodable {
case lowPassFilter
}
}
extension InstrumentsSet.Track.Effect: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: EffectKeys.self)
//FIXME: This is the location of the error: Type 'ValueAndRange.Type' cannot conform to 'Encodable'
try container.encode(ValueAndRange.self, forKey: .cutoffFrequency)
}
}
The problem is ValueAndRange.self not not conforming to Encodable
I've followed multiple examples to get to this implementation:
import Foundation
import AudioKit
struct ValueAndRange: Encodable {
private enum ValueRangeKeys: String, CodingKey {
case value
case range
}
static var zero: ValueAndRange { .init(value: 0, range: [0, 0]) }
var value: AUValue
var range: [Double]
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: ValueRangeKeys.self)
try container.encode(value, forKey: .value)
try container.encode(range, forKey: .range)
}
}
extension ValueAndRange: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ValueRangeKeys.self)
value = try container.decode(AUValue.self, forKey: .value)
range = try container.decode([Double].self, forKey: .range)
}
}
I cannot see why this struct should not conform to Encodable. Maybe any of you got betters eyes (and brains) then I got?
Your encode function is incorrectly trying to encode a type, ValueAndRange.self, rather than a value.
Looking at the init(from:) method I think your encode function should look something like this
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: EffectKeys.self)
switch self {
case .lowPassFilter(let effect):
try container.encode(effect.cutOffFrequency, forKey: .cutoffFrequency)
try container.encode(effect.resonance, forKey: .resonance)
}
}
I didn't include .effectType in this code since I am uncertain of its usage (isn't it always the same hard coded string?).

how do you set nil to codable enum when unexpected value in swift iOS

I'm using Codable for my WebRequest response which is returning some predefined string or number. So, I'm using Enums for those. But when some unexpected value arrive to Response at that my Codable fails to decode.
Here some code for better understanding.
class WebUser: Codable, Equatable {
static func == (lhs: WebUser, rhs: WebUser) -> Bool {
return lhs.id == rhs.id
}
...
var mobileNumberPrivacy: CommonPrivacyOption?
var emailPrivacy: CommonPrivacyOption?
var dobPrivacy: CommonPrivacyOption?
...
}
enum CommonPrivacyOption: Int, CaseIterable, Codable {
case privacyOnlyMe = 1, privacyPublic, privacyFriends, privacyFriendsOfFriends
//Does not help this optional init function
/*init?(rawValue: Int) {
switch rawValue {
case 1: self = .privacyOnlyMe
case 2: self = .privacyPublic
case 3: self = .privacyFriends
case 4: self = .privacyFriendsOfFriends
default: return nil
}
}*/
}
but sometimes from WebServer I'm getting, 0 for dobPrivacy at that time I'm getting DecodingError.dataCorrupted exception with context Cannot initialize CommonPrivacyOption from invalid Int value 0
As I expect to dobPrivacy nil when I get other values then 1/2/3/4.
EDIT:
let dict1 = [
"id": 2,
"mobileNumberPrivacy": 3,
"emailPrivacy": 4,
"dobPrivacy": 0 // Works perfectly with 1
]
do {
let data1 = try JSONSerialization.data(withJSONObject: dict1, options: .prettyPrinted)
let user1 = try JSONDecoder().decode(WebUser.self, from: data1)
print("User1 created")
}
catch DecodingError.dataCorrupted(let context) {
print(context.codingPath)
print(context.debugDescription)
}
catch {
print(error.localizedDescription)
}
I'm using this same Codable WebUser object for Profile detail, search users and many more.
so may be some times one more key will not present in WebRequest's response.
I recommend writing a property wrapper that handles this problem for you.
Specifically, let's write a property wrapper named NilOnDecodingError that turns any DecodingError into nil.
Here's the declaration of NilOnDecodingError:
#propertyWrapper
public struct NilOnDecodingError<Wrapped> {
public init(wrappedValue: Wrapped?) {
self.wrappedValue = wrappedValue
}
public var wrappedValue: Wrapped?
}
We've defined it to wrap any type, storing an Optional.
Now we can conform it to Decodable when the Wrapped type is Decodable:
extension NilOnDecodingError: Decodable where Wrapped: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
wrappedValue = .some(try container.decode(Wrapped.self))
} catch is DecodingError {
wrappedValue = nil
}
}
}
We probably also want it to be Encodable when the Wrapped type is Encodable:
extension NilOnDecodingError: Encodable where Wrapped: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let value = wrappedValue {
try container.encode(value)
} else {
try container.encodeNil()
}
}
}
Now we can wrap the appropriate fields of WebUser:
class WebUser: Codable {
let id: String
#NilOnDecodingError
var mobileNumberPrivacy: CommonPrivacyOption?
#NilOnDecodingError
var emailPrivacy: CommonPrivacyOption?
#NilOnDecodingError
var dobPrivacy: CommonPrivacyOption?
}
For testing, we'll want to print the fields of the decoded user:
extension WebUser: CustomStringConvertible {
var description: String {
return """
WebUser(
id: \(id),
mobileNumberPrivacy: \(mobileNumberPrivacy.map { "\($0)" } ?? "nil"),
emailPrivacy: \(emailPrivacy.map { "\($0)" } ?? "nil")),
dobPrivacy: \(dobPrivacy.map { "\($0)" } ?? "nil")))
"""
}
}
Now we can try it out:
let json = """
{
"id": "mrugesh",
"mobileNumberPrivacy": 1,
"emailPrivacy": 2,
"dobPrivacy": 1000
}
"""
let user = try! JSONDecoder().decode(WebUser.self, from: json.data(using: .utf8)!)
print(user)
Output:
WebUser(
id: mrugesh,
mobileNumberPrivacy: privacyOnlyMe,
emailPrivacy: privacyPublic),
dobPrivacy: nil))
You need to create a custom Decodable initializer inside WebUser:
class WebUser: Codable {
var dobPrivacy: CommonPrivacyOption?
// Rest of your properties here.
enum CodingKeys: String, CodingKey {
case dobPrivacy
// Add a case for each property you want to decode here.
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Use optional try to decode your enum so that when the
// decode fails because of wrong Int value, it will assign nil.
dobPrivacy = try? container.decode(CommonPrivacyOption.self, forKey: .dobPrivacy)
}
}
Alternatively, you can implement the Decodable initializer inside CommonPrivacyOption and add an additional case unknown like so:
enum CommonPrivacyOption: Int, Codable {
case privacyOnlyMe = 1
case privacyPublic, privacyFriends, privacyFriendsOfFriends
case unknown
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let value = try container.decode(Int.self)
// Try to initialize Self from value, if
// value is not 1, 2, 3, or 4, initialize Self to
// the unknown case.
self = .init(rawValue: value) ?? .unknown
}
}
It looks to me like the compiler selects the wrong init for the enum types, instead of init(rawValue) it uses init(from:) that is the one for decoding (which in a way makes sense)
Here is a solution where we override this behaviour by using a custom init(from) in WebUser that decodes the raw values and then creates an enum item
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .mobileNumberPrivacy), let mobileNumberPrivacy = CommonPrivacyOption(rawValue: value) {
self.mobileNumberPrivacy = mobileNumberPrivacy
}
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .emailPrivacy), let emailPrivacy = CommonPrivacyOption(rawValue: value) {
self.emailPrivacy = emailPrivacy
}
if let value = try container.decodeIfPresent(CommonPrivacyOption.RawValue.self, forKey: .dobPrivacy), let dobPrivacy = CommonPrivacyOption(rawValue: value) {
self.dobPrivacy = dobPrivacy
}
}
Below is a small example
extension WebUser: CustomStringConvertible {
var description: String {
"Mobile: \(mobileNumberPrivacy?.rawValue), email: \(emailPrivacy?.rawValue), dob: \(dobPrivacy?.rawValue)"
}
}
let data = """
{
"mobileNumberPrivacy": 1,
"dobPrivacy": 0
}
""".data(using: .utf8)!
do {
let result = try JSONDecoder().decode(WebUser.self, from: data)
print(result)
} catch {
print(error)
}
Mobile: Optional(1), email: nil, dob: nil
Of course if you can change your mind about 0 being translated to nil then I would suggest you extend the enum to support the 0 value
enum CommonPrivacyOption: Int, CaseIterable, Codable {
case none = 0
case privacyOnlyMe = 1, privacyPublic, privacyFriends, privacyFriendsOfFriends
}
Then it should work out of the box and you don't need to write any custom code.
I liked #alobaili's answer since it's simple and provided a good solution. One thing I wanted to improve is to make it more generic, so that any Decodable (and Codable) could do it with less code to write.
extension Decodable where Self: RawRepresentable, RawValue: Decodable {
static func initializedOptionalWith(decoder: Decoder, defaultValue: Self) throws -> Self {
let container = try decoder.singleValueContainer()
let value = try container.decode(RawValue.self)
return .init(rawValue: value) ?? defaultValue
}
}
Usage:
init(from decoder: Decoder) throws {
self = try .initializedOptionalWith(decoder: decoder, defaultValue: .unknown)
}
Thank you Rob Mayoff for a great answer. I would like to add just one thing - if the property is missing, decoding fails even if the property is optional. To prevent this failure, add the following extension:
extension KeyedDecodingContainer {
func decode<T>(_ type: NilOnDecodingError<T>.Type, forKey key: Self.Key) throws -> NilOnDecodingError<T> where T: Decodable {
try decodeIfPresent(type, forKey: key) ?? NilOnDecodingError<T>(wrappedValue: nil)
}
}

Automatic decodable synthesis for decodable property wrappers

Let's say I have decodable property wrapper:
#propertyWrapper
struct OptionalDecodable<Value: Decodable>: Decodable {
var wrappedValue: Value?
}
The compiler does synthesize init for the following
struct Model: Decodable {
#OptionalDecodable private(set) var string: String?
}
To test if this works I just try to decode empty JSON (i.e. "{}")
However, string property is not treated as optional, i.e. when there's no string key I get an error that key was not found.
Is there a work around this?
I'm not sure if this is the best way, but the issue is that wrappedValue type of the property wrapper has to match the property's type, and String is different than String?.
One approach to overcome this is to make the property wrapper generic, but constrain in such a way that would allow you to initialize the type from a String or an Int:
protocol ExpressibleByString {
init(fromString: String)
}
extension String: ExpressibleByString {
init(fromString: String) { self = fromString }
}
extension Optional: ExpressibleByString where Wrapped == String {
init(fromString: String) { self = fromString }
}
#propertyWrapper
struct IntOrString<V: ExpressibleByString & Decodable>: Decodable {
var wrappedValue: V
}
extension IntOrString {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let int = try container.decode(Int.self)
wrappedValue = .init(fromString: int.description)
} catch DecodingError.typeMismatch {
wrappedValue = try .init(fromString: container.decode(String.self))
}
}
}
extension KeyedDecodingContainer {
func decode<V: ExpressibleByNilLiteral>(_ t: IntOrString<V>.Type, forKey key: K) throws -> IntOrString<V> {
if let v = try decodeIfPresent(t, forKey: key) {
return v
}
return IntOrString(wrappedValue: nil)
}
}
Then you could use it on both optional and non-optional String:
struct Foo: Decodable {
#IntOrString
var p1: String?
#IntOrString
var p2: String
}

use CodingKeys to access the a value of a variable of a struct

I am creating a custom JSON decoding method for my struct and I need to loop over all its parameters. I thought about using CodingKeys but I dont know how to achieve it ( or even if it is feasible ).
Here is the code I am trying to reduce :
struct Root: Codable {
let objectA: ObjectA?
let objectB: ObjectB?
enum CodingKeys: String, CodingKey {
case objectA
case objectB
}
init(from decoder: Decoder) throws {
objectA = decodeOrSkip(using: decoder, key: .objectA)
objectB = decodeOrSkip(using: decoder, key: .objectB)
// ...
}
private func decodeOrSkip<T: Codable>(using decoder: Decoder, key: CodingKeys) -> T? {
guard let container = try? decoder.container(keyedBy: CodingKeys.self) else { return nil }
if let decoded = try? container.decode(T.self, forKey: key) {
return decoded
} else {
log.warning("Unable to decode \(T.self) from \(String(describing: self))")
return nil
}
}
}
struct ObjectA: Codable { /*...*/ }
struct ObjectB: Codable { /*...*/ }
if I could use CodingKeys to access the a value of a variable of a struct, I could loop over my CodingKeys (by making it conform to CaseIterable) and apply decodeOrSkip for each of them
During my researches I found this post : Swift Keypath from String that states :
Finally I found out that I should use CodingKeys instead of KeyPaths to access the a value of a variable of a struct by String
But no code is shown on how to achieve it.