0

I have a downloaded JSON file. Within it, there are repeating objects that are either 1 of 2 types - either a [Double] or a [[[Double]]].

I am trying to use a Codable protocol for a custom struct to dump the data into objects. To get around the above, I have actually cast the simpler [Double] into the same [[[Double]]] type.

Later on, when using this data, I am struggling casting this back to a simpler single-tier array. I hoped I could force cast this back to the single as! [Double] type. How else can I do this? Would I need a "for in" loop for each array tier?

Alternatively, how can I adjust my Geometry struct so I don't mess about with different types of arrays for this property? I was wondering if the coordinates property could be changed to be of type Any, or some other type?

struct Geometry: Codable {
    let coordinates: [[[Double]]]
    let type: String

    enum CodingKeys: String, CodingKey {
        case coordinates
        case type
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        type = try container.decode(String.self, forKey: .type)

        if type == "Point" {
            let typeFromMapbox = try container.decode([Double].self, forKey: .coordinates)
            coordinates = [[typeFromMapbox]]
        } else { // THIS IS A POLYGON
            coordinates = try container.decode([[[Double]]].self, forKey: .coordinates)
        }
    }
}

I am only starting & learning with Swift

Appreciate any help, pointers or insights

Thanks

1
  • Post the JSON. It will make it easier for everyone to help you. Commented Mar 18, 2018 at 23:40

1 Answer 1

2

You didn't provide the actual JSON. So, I assumed it somewhat as:

{
    "geometries" :
    [
        {
            "coordinates" : [1.0, 2.0],
            "type" : "flat"
        },
        {
            "coordinates" : [[[111.0, 222.0]]],
            "type" : "multi"
        }
    ]
}

Based on the above structure, your root level data type would be:

struct Root: Codable {
    let geometries: [Geometry]
}

Then, your Geometry would be defined as:

struct Geometry: Codable {
    // As long as your coordinates should be at least a flat array. Multi dimensional array will be handled by `Coordinate` type
    let coordinates: [Coordinate]
    let type: String
}

Now comes the Coordinate array. This may be a Double type or a [[Double]] type. So wrap it with an enum:

enum Coordinate: Codable {
    case double(Double)
    case arrayOfDoubleArray([[Double]])

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self = try .double(container.decode(Double.self))
        } catch DecodingError.typeMismatch {
            do {
                self = try .arrayOfDoubleArray(container.decode([[Double]].self))
            } catch DecodingError.typeMismatch {
                throw DecodingError.typeMismatch(Coordinate.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Coordinate type doesn't match"))
            }
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .double(let double):
            try container.encode(double)
        case .arrayOfDoubleArray(let arrayOfDoubleArray):
            try container.encode(arrayOfDoubleArray)
        }
    }
}

As long as your keys in the JSON and the properties in your struct are identical, you don't need to provide CodingKeys.

Now decode:

let jsonData = """
{
    "geometries" :
    [
        {
            "coordinates" : [1.0, 2.0],
            "type" : "flat"
        },
        {
            "coordinates" : [[[111.0, 222.0]]],
            "type" : "multi"
        }
    ]
}
""".data(using: .utf8)!

do {
    let root = try JSONDecoder().decode(Root.self, from: jsonData)
    root.geometries.forEach({ (geometry) in
        geometry.coordinates.forEach({ (coordinate) in
            if case .double(let double) = coordinate {
                print(double)   // 1.0, 2.0
            }
            if case .arrayOfDoubleArray(let array) = coordinate {
                print(array)    // [[111.0, 222.0]]
            }
        })
    })
} catch {
    print(error)
}
1
  • .@nayem, thanks for the response. You guessed the JSON format correctly, so I won't provide another sample. It seems you are doing more "work" during the encoding & decoding, and it doesn't seem much less effort than leaving both types as triple-arrays and unwrapping as needed when needed. I was mostly enquiring if there was a special / shortcut / preferred technique.
    – jingo_man
    Commented Mar 19, 2018 at 22:04

Not the answer you're looking for? Browse other questions tagged or ask your own question.