Search code examples
swiftcodable

How to add variable to a struct which does not part of decodable?


I would use nodesWithDepth out of decodable:

this worked before:

public struct NEWTREE: Equatable, Codable {
    public var Filename: String
    public var GROUP: [GROUP]
    public var ITEM: [ITEM]
    public var CATEGORY: [CATEGORY]
    public var ROOT: ROOT

but the modified not:

public struct NEWTREE: Equatable, Codable {
    public var Filename: String
    public var GROUP: [GROUP]
    public var ITEM: [ITEM]
    public var CATEGORY: [CATEGORY]
    public var ROOT: ROOT
    public var nodesWithDepth: [(text: String, depth: Int, type: TreeData2)]?
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        Filename = try container.decode(String.self, forKey: .Filename)
        GROUP = try container.decode([GROUP].self, forKey: .GROUP)
        ITEM = try container.decode([ITEM].self, forKey: .ITEM)
        CATEGORY = try container.decode([CATEGORY].self, forKey: .CATEGORY)
        ROOT = try container.decode(ROOT.self, forKey: .ROOT)
    }
    
    private enum CodingKeys: String, CodingKey {
        case Filename
        case GROUP
        case ITEM
        case CATEGORY
        case ROOT
    }

but this raise an error:

Cannot convert value of type '[[GROUP]]' to expected argument type '[GROUP].Type'

How can I fix?


Solution

  • Swift is confused by expressions such as

    [GROUP].self
    

    Apparently, the compiler thinks that GROUP here refers to the property GROUP, rather than the type GROUP. Note that it is totally valid to add .self to any expression, without changing what it means.

    So effectively, you are passing a one-element array, containing the value of the property GROUP. This expression has the type [[GROUP]], but decode obviously expects a decodable metatype, hence the error.

    Or in the case of ROOT, you are passing the value of the ROOT property, which has the type ROOT, but decode obviously expects a decodable metatype.

    This behaviour can be shown with a more minimal example like this:

    struct A {}
    struct B {
        let A: A
    
        func f() {
            print(A.self)
            print([A].self)
        }
    }
    
    B(A: A()).f()
    print("-------")
    print(A.self)
    print([A].self)
    

    The two print statements outside B prints metatypes, whereas the ones inside B prints an instance of A and an array.


    You can solve this ambiguity by using the long form of the array type, Array<Group>.self

    GROUP = try container.decode(Array<GROUP>.self, forKey: .GROUP)
    

    Or introduce a type alias in cases such as ROOT:

    typealias ROOTType = ROOT
    
    ...
    
    ROOT = try container.decode(ROOTType.self, forKey: .ROOT)
    

    But of course, none of this would have happened if you adhered to the Swift naming guidelines, and did:

    var groups: [Group]
    

    If you want the coding keys to all be capitalised, you can do it in the coding keys enum:

    public struct NewTree: Equatable, Codable {
        public var filename: String
        public var groups: [Group]
        public var items: [Item]
        public var categories: [Category]
        public var root: Root
    
        // consider making the tuple here into another struct...
        public var nodesWithDepth: [(text: String, depth: Int, type: TreeData2)]?
    
        // you don't actually need to write the custom decoding logic just because you added nodesWithDepth
        // Swift can figure it out from the CodingKeys enum because the
        // case names match the property names
    
        private enum CodingKeys: String, CodingKey {
            case filename = "Filename"
            case groups = "GROUP"
            case items = "ITEM"
            case categories = "CATEGORY"
            case root = "ROOT"
        }
    }