Search code examples
swiftgeojsoncodable

Swift Codable Type encodes successfully but fails to decode


I have an enum for decoding points, polygons, and multipolygons from GeoJSON files using several nested Codable types. I can decode GeoJSON files without issue, but when I encode this data to store as binary data in Core Data, it can't be decoded again.

I'm getting typeMismatch when trying to decode.

Here's my enum:

enum GeoJSONFeatureGeometryCoordinates: Codable {
    
    case point([Double])
    case polygon([[[Double]]])
    case multipolygon([[[[Double]]]])

    
    init(from decoder: Decoder) throws {
        
        let container = try decoder.singleValueContainer()
        
        do {
            
            let polygonVal = try container.decode([[[Double]]].self)
            self = .polygon(polygonVal)
        } catch DecodingError.typeMismatch {
            do {
                
                let multipolygonVal = try container.decode([[[[Double]]]].self)
                self = .multipolygon(multipolygonVal)
            } catch DecodingError.typeMismatch {
                
                let pointVal = try container.decode([Double].self)
                self = .point(pointVal)
            }
        }
    }
}

And here's where I successfully encode it but throw an error when testing the decoding


        do {
            let encoder = JSONEncoder()
            let encodedCoordinates = try encoder.encode(centroid.geometry.coordinates)
            
            let decoder = JSONDecoder()
            let decodedCoordinates = try decoder.decode(GeoJSONFeatureGeometryCoordinates.self, from: encodedCoordinates)
        } catch {
            fatalError("\n\n Error archiving feature.geometry.coordinates as Data")
        }

Solution

  • The decoding fails because you are encoding it in one format and decoding it in another format.

    Since you did not provide an encode(to:) implementation, an implementation is automatically generated by the Swift compiler. This implementation encodes your enum to a format like this:

    {
        "point": { 
            "_0":  [] // the Double array goes here
        }
    }
    

    Notice how the name of the enum case, as well as the names of the associated values are also recorded. This is different from the format that your init(from:) implementation expects. Therefore decoding fails.

    You can add an explicit implementation of encode(to:), so that it produces the format that init(from:) expects.

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .point(let arr):
            try container.encode(arr)
        case .polygon(let arr):
            try container.encode(arr)
        case .multipolygon(let arr):
            try container.encode(arr)
        }
    }