35

I'm using an API that returns this pretty horrible JSON structure:

[
  "A string",
  [
    "A string",
    "A string",
    "A string",
    "A string",
    …
  ]
]

I'm trying to decode the nested array using JSONDecoder, but it doesn't have a single key and I really don't know where to start...

3 Answers 3

33

If the structure stays the same, you can use this Decodable approach.

First create a decodable Model like this:

struct MyModel: Decodable {
    let firstString: String
    let stringArray: [String]

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        firstString = try container.decode(String.self)
        stringArray = try container.decode([String].self)
    }
}

Or if you really want to keep the JSON's structure, like this:

struct MyModel: Decodable {
    let array: [Any]

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        let firstString = try container.decode(String.self)
        let stringArray = try container.decode([String].self)
        array = [firstString, stringArray]
    }
}

And use it like this

let jsonString = """
["A string1", ["A string2", "A string3", "A string4", "A string5"]]
"""
if let jsonData = jsonString.data(using: .utf8) {
    let myModel = try? JSONDecoder().decode(MyModel.self, from: jsonData)
}
Sign up to request clarification or add additional context in comments.

Comments

9

This is a bit interesting for decoding.

You don't have any key. So it eliminates the need of a wrapper struct.

But look at the inner types. You get mixture of String and [String] types. So you need something that deals with this mixture type. You would need an enum to be precise.

// I've provided the Encodable & Decodable both with Codable for clarity. You obviously can omit the implementation for Encodable
enum StringOrArrayType: Codable {
    case string(String)
    case array([String])

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self = try .string(container.decode(String.self))
        } catch DecodingError.typeMismatch {
            do {
                self = try .array(container.decode([String].self))
            } catch DecodingError.typeMismatch {
                throw DecodingError.typeMismatch(StringOrArrayType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type"))
            }
        }
    }

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

Decoding Process:

let json = """
[
  "A string",
  [
    "A string",
    "A string",
    "A string",
    "A string"
  ]
]
""".data(using: .utf8)!

do {
    let response = try JSONDecoder().decode([StringOrArrayType].self, from: json)
    // Here, you have your Array
    print(response) // ["A string", ["A string", "A string", "A string", "A string"]]

    // If you want to get elements from this Array, you might do something like below
    response.forEach({ (element) in
        if case .string(let string) = element {
            print(string) // "A string"
        }
        if case .array(let array) = element {
            print(array) // ["A string", "A string", "A string", "A string"]
        }
    })
} catch {
    print(error)
}

Comments

4

A possible solution is to use the JSONSerialization, then you might simply dig inside such json, doing so:

import Foundation

let jsonString = "[\"A string\",[\"A string\",\"A string\", \"A string\", \"A string\"]]"
if let jsonData = jsonString.data(using: .utf8) {
    if let jsonArray = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [Any] {
        jsonArray.forEach {
            if let innerArray = $0 as? [Any] {
                print(innerArray) // this is the stuff you need
            }
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.