Hot to Decode JSON Data That Could and Array or a Single Element in Swift

Hot to decode JSON data that could and array or a single element in Swift?

Your JSON actually is either a String or an array of Strings. So you need to create a custom decoder to decode and then convert them to Double:

struct Info {
let author, title: String
let tags: [Tags]
let price: [Double]
enum Tags: String, Codable {
case nonfiction, biography, fiction
}
}

extension Info: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
author = try container.decode(String.self, forKey: .author)
title = try container.decode(String.self, forKey: .title)
tags = try container.decode([Tags].self, forKey: .tags)
do {
price = try [Double(container.decode(String.self, forKey: .price)) ?? .zero]
} catch {
price = try container.decode([String].self, forKey: .price).compactMap(Double.init)
}
}
}

Playground testing

let infoData = Data("""
{
"author" : "Mark A",
"title" : "The Great Deman",
"tags" : [
"nonfiction",
"biography"
],
"price" : "242"

}
""".utf8)
do {
let info = try JSONDecoder().decode(Info.self, from: infoData)
print("price",info.price) // "price [242.0]\n"
} catch {
print(error)
}

let infoData2 = Data("""
{
"author" : "Mark A",
"title" : "The Great Deman",
"tags" : [
"nonfiction",
"biography"
],
"price" : [
"242",
"299",
"335"
]

}
""".utf8)

do {
let info = try JSONDecoder().decode(Info.self, from: infoData2)
print("price",info.price) // "price [242.0, 299.0, 335.0]\n"
} catch {
print(error)
}

Decoding JSON for single object vs array of objects in swift

You can create your own decoder,

struct Response: Decodable {
let totalItems: Int
let pageSize: Int
let venues: VenueWrapper

struct VenueWrapper: Decodable {
var venue: [Venue]

init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
venue = []
if let singleVenue = try? values.decode(Venue.self, forKey: CodingKeys.venue) {
//if a single venue decoded append it to array
venue.append(singleVenue)
} else if let multiVenue = try? values.decode([Venue].self, forKey: CodingKeys.venue) {
//if a multi venue decoded, set it as venue
venue = multiVenue
}

enum CodingKeys: String, CodingKey { case venue }
}
}

struct Venue: Decodable {
let name: String
let location: String
let showCount: String
}
}

Decode json array as an object in swift

First, I assume you'd really like the final result to be [Product] where Product looks like this:

struct Product {
var name: String = ""
var quantity: Int = 0
}

So if any keys are missing, they'll get the defaults. You could change this solution to throw an error in that case, but I think this approach is much nicer than Optionals.

Given that, here's how you decode it:

extension Product: Decodable {
init(from decoder: Decoder) throws {
// This is a very uniform type (all Strings). Swift can decode that
// without much help.
let container = try decoder
.singleValueContainer()
.decode([String: [[String: String]]].self)

// Extract the Products. Throwing an error here might be nicer.
let keyValues = container["Product"] ?? []

// This loops over every Key-Value element, and then loops through
// each one. We only expect one element in the second loop, though.
for keyValue in keyValues {
for (key, value) in keyValue {
switch key {
case "Name": self.name = value
case "Quantity": self.quantity = Int(value) ?? 0
default: break // Ignore unknown keys
}
}
}
// This solution just assigns defaults for missing keys, but
// you could validate that everything was found, and throw an
// error here if desired.
}
}

let data = try JSONDecoder().decode([Product].self, from: jsonData)
// [{name "exampleName", quantity 1}]

data.first!.name
// "exampleName"

In most cases the above is probably fine, but it's also very sloppy about malformed data. It just returns a default object, which could make an error very hard to track down. This example goes in the other direction, and does all the error checking you would expect from a normal Decodable.

First, we'll need a CodingKey that can accept any String. I really don't understand why this isn't built into stdlib:

struct AnyStringKey: CodingKey, Hashable, ExpressibleByStringLiteral {
var stringValue: String
init(stringValue: String) { self.stringValue = stringValue }
init(_ stringValue: String) { self.init(stringValue: stringValue) }
var intValue: Int?
init?(intValue: Int) { return nil }
init(stringLiteral value: String) { self.init(value) }
}

I also find DecodingErrors very cumbersome to build, so I often build little helper functions:

func keyNotFound(_ key: String, codingPath: [CodingKey]) -> Error {
DecodingError.keyNotFound(AnyStringKey(key),
.init(codingPath: [],
debugDescription: "\(key) key not found"))
}


func typeMismatch(_ key: String, expected: Any.Type, codingPath: [CodingKey]) -> Error {
DecodingError.typeMismatch(expected, .init(codingPath: codingPath + [AnyStringKey(key)],
debugDescription: "Expected \(expected)."))
}

With those in place, here's a more strict decoder:

extension Product: Decodable {
init(from decoder: Decoder) throws {
// This is a very uniform type (all Strings). Swift can decode that
// without much help.
let container = try decoder
.singleValueContainer()
.decode([String: [[String: String]]].self)

var codingPath: [AnyStringKey] = []

// Extract the Products. Throwing an error here might be nicer.
guard let keyValues = container["Product"] else {
throw keyNotFound("Product", codingPath: codingPath)
}

codingPath.append("Product")

var name: String?
var quantity: Int?

// This loops over every Key-Value element, and then loops through
// each one. We only expect one element in the second loop, though.
for keyValue in keyValues {
for (key, value) in keyValue {
switch key {
case "Name":
name = value

case "Quantity":
guard let intValue = Int(value) else {
throw typeMismatch("Quantity",
expected: Int.self,
codingPath: codingPath)
}
quantity = intValue

default: break // Ignore unknown keys
}
}
}

guard let name = name else {
throw keyNotFound("Name", codingPath: codingPath)
}
self.name = name

guard let quantity = quantity else {
throw keyNotFound("Quantity", codingPath: codingPath)
}
self.quantity = quantity
}
}

Decode JSON single object or array of object dynamically

One solution could be to always return [Model]?.

Inside your function first try to decode as Model, on success return an array with that single decoded object inside it. If this fails then try to decode as [Model], on success return the decoded object else return nil.

Using your sample JSONs I created a struct:

struct Person: Codable {
let id, name: String

enum CodingKeys: String, CodingKey {
case id
case name = "Name"
}
}

Then I created a struct with a couple of methods to decode from either a String or an optional Data.

struct Json2Type<T: Decodable> {
// From data to type T
static public func convertJson(_ data: Data?) -> [T]? {
// Check data is not nil
guard let data = data else { return nil }
let decoder = JSONDecoder()
// First try to decode as a single object
if let singleObject = try? decoder.decode(T.self, from: data) {
// On success return the single object inside an array
return [singleObject]
}
// Try to decode as multiple objects
guard let multipleObjects = try? decoder.decode([T].self, from: data) else { return nil }
return multipleObjects
}

// Another function to decode from String
static public func convertJson(_ string: String) -> [T]? {
return convertJson(string.data(using: .utf8))
}
}

Finally call the method you prefer:

Json2Type<Person>.convertJson(JsonAsDataOrString)

Update: @odin_123, a way to have either a Model or [Model] as return value can be accomplish using an enum. We can even add the error condition there to avoid returning optionals. Let's define the enum as:

enum SingleMulipleResult<T> {
case single(T)
case multiple([T])
case error
}

Then the struct changes to something like this:

struct Json2Type<T: Decodable> {
static public func convertJson(_ data: Data?) -> SingleMulipleResult<T> {
guard let data = data else { return .error }
let decoder = JSONDecoder()
if let singleObject = try? decoder.decode(T.self, from: data) {
return .single(singleObject)
}
guard let multipleObjects = try? decoder.decode([T].self, from: data) else { return .error }
return .multiple(multipleObjects)
}

static public func convertJson(_ string: String) -> SingleMulipleResult<T> {
return convertJson(string.data(using: .utf8))
}
}

You can call it the same way we did before:

let response = Json2Type<Person>.convertJson(JsonAsDataOrString)

And use a switch to check every possible response value:

switch(response) {
case .single(let object):
print("One value: \(object)")
case .multiple(let objects):
print("Multiple values: \(objects)")
case .error:
print("Error!!!!")
}

How to decode JSON Array with different objects with Codable in Swift?


What I want to achive is that one struct as a wrapper and struct for (Id, ServicePublicKey, Token etc..).
At the end when there is a new type coming from JSON, I need to write only relevant struct and add some code inside wrapper.
My Question is that: How can I parse this JSON without any optional variable?

First of all I totally agree with your idea.
When decoding a JSON we should always aim to

  • no optionals (as long as this is guaranteed by the backend)
  • easy extendibility

Let's start

So given this JSON

let data = """
{
"Response": [
{
"Id": {
"id": 123456
}
},
{
"Token": {
"token": "myToken",
"updated": "2020-01-11 13:55:43.397764",
"created": "2020-01-11 13:55:43.397764",
"id": 123456
}
},
{
"ServerPublicKey": {
"server_public_key": "some key"
}
}
]
}
""".data(using: .utf8)!

ID Model

struct ID: Decodable {
let id: Int
}

Token Model

struct Token: Decodable {
let token: String
let updated: String
let created: String
let id: Int
}

ServerPublicKey Model

struct ServerPublicKey: Decodable {
let serverPublicKey: String
enum CodingKeys: String, CodingKey {
case serverPublicKey = "server_public_key"
}
}

Result Model

struct Result: Decodable {

let response: [Response]

enum CodingKeys: String, CodingKey {
case response = "Response"
}

enum Response: Decodable {

enum DecodingError: Error {
case wrongJSON
}

case id(ID)
case token(Token)
case serverPublicKey(ServerPublicKey)

enum CodingKeys: String, CodingKey {
case id = "Id"
case token = "Token"
case serverPublicKey = "ServerPublicKey"
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
switch container.allKeys.first {
case .id:
let value = try container.decode(ID.self, forKey: .id)
self = .id(value)
case .token:
let value = try container.decode(Token.self, forKey: .token)
self = .token(value)
case .serverPublicKey:
let value = try container.decode(ServerPublicKey.self, forKey: .serverPublicKey)
self = .serverPublicKey(value)
case .none:
throw DecodingError.wrongJSON
}
}
}
}

Let's decode!

We can finally decode your JSON

do {
let result = try JSONDecoder().decode(Result.self, from: data)
print(result)
} catch {
print(error)
}

Output

And this is the output

Result(response: [
Result.Response.id(
Result.Response.ID(
id: 123456
)
),
Result.Response.token(
Result.Response.Token(
token: "myToken",
updated: "2020-01-11 13:55:43.397764",
created: "2020-01-11 13:55:43.397764",
id: 123456)
),
Result.Response.serverPublicKey(
Result.Response.ServerPublicKey(
serverPublicKey: "some key"
)
)
])

Date Decoding

I leave the date decoding to you as homework ;-)

UPDATE

This additional part should answer to your comment

Can we store variables like id, serverPublicKey inside Result struct
without Response array. I mean instead of ResponseArray can we just
have properties? I think It need a kind of mapping but I can't figure
out.

Yes, I think we can.

We need to add one more struct to the ones already described above.

Here it is

struct AccessibleResult {

let id: ID
let token: Token
let serverPublicKey: ServerPublicKey

init?(result: Result) {

typealias ComponentsType = (id: ID?, token: Token?, serverPublicKey: ServerPublicKey?)

let components = result.response.reduce(ComponentsType(nil, nil, nil)) { (res, response) in
var res = res
switch response {
case .id(let id): res.id = id
case .token(let token): res.token = token
case .serverPublicKey(let serverPublicKey): res.serverPublicKey = serverPublicKey
}
return res
}

guard
let id = components.id,
let token = components.token,
let serverPublicKey = components.serverPublicKey
else { return nil }

self.id = id
self.token = token
self.serverPublicKey = serverPublicKey
}
}

This AccessibleResult struct has an initialiser which receives a Result value and tries to populated its 3 properties

let id: ID
let token: Token
let serverPublicKey: ServerPublicKey

If everything goes fine, I mean if the input Result contains at least an ID, a Token and a ServerPublicKey then the AccessibleResponse is initialised, otherwise the init fails and nil` is returned.

Test

if
let result = try? JSONDecoder().decode(Result.self, from: data),
let accessibleResult = AccessibleResult(result: result) {
print(accessibleResult)
}

How do I decode JSON in Swift when it's an array and the first item is a different type than the rest?

You can implement a custom decoder for your Result struct.

init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()

// Assume the first one is a Dog
self.dog = try container.decode(Dog.self)

// Assume the rest are Turtle
var turtles = [Turtle]()

while !container.isAtEnd {
let turtle = try container.decode(Turtle.self)
turtles.append(turtle)
}

self.turtles = turtles
}

With a minor amount of work you could support the Dog dictionary being anywhere within the array of Turtle dictionaries.

Since you declared that your structs are Codable and not just Decodable, you should also implement the custom encode(to:) from Encodable but that is an exercise left to the reader.

How to Json decode API data with an array?

result is indeed a single object, but the property results is an array (multiple objects).

A slightly different naming avoids the confusion.

Notes:

  • Never print literal "error" or error.localizedDescription in a Decoding context, always print the error instance.

  • Proceed to parse the result in the do scope

      private func getData(from url: String){
    guard let url = URL(string: url) else { print("Bad URL", url); return }
    let task = URLSession.shared.dataTask(with: url) {data, _, error in
    if let error = error { print(error); return }
    do {
    let response = try JSONDecoder().decode(Response.self, from: data!)
    for result in response.results {
    print(result.latitude)
    print(result.longitude)
    print(result.elevation)
    }
    }
    catch {
    print(error)
    }
    }
    task.resume()
    }

swift: How can I decode an array of json objects, without creating a struct, that holds an array of said objects?

If you find yourself needing this multiple times, then you can build your own generic struct that decodes over whichever key it finds:

struct Nester<T: Decodable>: Decodable {
let elements: [T]

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let key = container.allKeys.first {
elements = try container.decode([T].self, forKey: key)
} else {
// we run into an empty dictionary, let's signal this
throw DecodingError.typeMismatch([String:Any].self, DecodingError.Context(codingPath: [], debugDescription: "Expected to find at least one key"))
}
}

// A coding key that accepts whatever string value it is given
struct CodingKeys: CodingKey {
let stringValue: String
var intValue: Int? { nil }

init?(stringValue: String) {
self.stringValue = stringValue
}

init?(intValue: Int) { return nil }
}
}

And with this in hand, you can extend JSONDecoder in order to get a nicer call site:

extension JSONDecoder {
func decode<T: Decodable>(nested: [T].Type, from data: Data) throws -> [T] {
try decode(Nester<T>.self, from: data).elements
}
}

Then it's just a matter of calling the new overload:

let places = try JSONDecoder().decode(nested: [Place].self, from: data)

P.S. if you want, you can hide the complex struct within the extension, resulting in something like this:

extension JSONDecoder {
func decode<T: Decodable>(nested: [T].Type, from data: Data) throws -> [T] {
try decode(Nester<T>.self, from: data).elements
}

private struct Nester<T: Decodable>: Decodable {
let elements: [T]

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let key = container.allKeys.first {
elements = try container.decode([T].self, forKey: key)
} else {
throw DecodingError.typeMismatch([String:Any].self, DecodingError.Context(codingPath: [], debugDescription: "Expected to find at least one key"))
}
}

struct CodingKeys: CodingKey {
let stringValue: String
var intValue: Int? { nil }

init?(stringValue: String) {
self.stringValue = stringValue
}

init?(intValue: Int) { return nil }
}
}
}

The downside is that you'll not be able to reuse the struct if you want to extend other decoders besides the JSON one.



Related Topics



Leave a reply



Submit