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

Time:05-13

[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?

CodePudding user response:

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?).

  • Related