Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decoding a JSON without keys in Swift 4

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

[   "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… Do you have any idea?

Thanks a lot!

like image 466
davidg Avatar asked Mar 07 '18 08:03

davidg


People also ask

How do I decode a JSON file?

You just have to use json_decode() function to convert JSON objects to the appropriate PHP data type. Example: By default the json_decode() function returns an object. You can optionally specify a second parameter that accepts a boolean value. When it is set as “true”, JSON objects are decoded into associative arrays.

What is Jsonserialization in Swift?

An object that converts between JSON and the equivalent Foundation objects.

What is JSON parsing in Swift?

Swift JSON ParsingJSON stands for JavaScript Object Notation. It's a popular text-based data format used everywhere for representing structured data. Almost every programming language supports it with Swift being no exception. You are going to use JSON a lot throughout your career, so make sure you don't miss out.


2 Answers

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) } 
like image 136
heyfrank Avatar answered Oct 10 '22 15:10

heyfrank


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) } 
like image 32
nayem Avatar answered Oct 10 '22 15:10

nayem