swiftenumscodableencodable

Add Encodable extension to Swift nested enum


I want to encode instances of the existing NWInterface class which has a type field of type NWInterface.InterfaceType which is an enum.

Is there a way to easily add the Encodable interface to that nested type? At the moment my workaround is to handle the encoding in an extension to the enclosing class:

extension NWInterface : Encodable {

    enum CodingKeys: CodingKey {
        case name
        case type
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        switch type {
            case .wifi:
                try container.encode("wifi", forKey: .type)
            case .cellular:
                try container.encode("cellular", forKey: .type)
            case .wiredEthernet:
                try container.encode("ethernet", forKey: .type)
            case .loopback:
                try container.encode("loopback", forKey: .type)
            case .other:
                try container.encode("other", forKey: .type)
            default:
                try container.encode("unknown", forKey: .type)
        }
    }
}

This feels unsatisfactory, especially since for my own defined enum types I can simply add the Encodable interface at the point of definition and get the implementation "for free".


Solution

  • Can you add a Encodable conformance to a nested enum?

    Yes

    Do you get it for free, like with your own enums?

    No. I don't know why, but automatic Codable synthesis seems to require that the extension be in the same file as the struct, class or enum.

    Here's what the implementation extending the nested enum would look like.

    extension NWInterface : Encodable {
        enum CodingKeys : CodingKey {
            case name, type
        }
        public func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(name, forKey: .name)
            try container.encode(type, forKey: .type)
        }
    }
    
    extension NWInterface.InterfaceType : Encodable {
        public func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
                case .wifi:
                    try container.encode("wifi")
                case .cellular:
                    try container.encode("cellular")
                case .wiredEthernet:
                    try container.encode("ethernet")
                case .loopback:
                    try container.encode("loopback")
                case .other:
                    try container.encode("other")
                default:
                    try container.encode("unknown")
            }
        }
    }
    

    You could also squeeze the second extension down to:

    extension NWInterface.InterfaceType : Encodable {
        public func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            try container.encode("\(self)")
        }
    }
    

    provided you take appropriate measures to handle unexpected strings in your Decoder.