How to Manually Decode an an Array in Swift 4 Codable

How to manually decode an an array in swift 4 Codable?

Your code doesn't compile due to a few mistakes / typos.

To decode an array of Int write

struct Something: Decodable {
var value: [Int]

enum CodingKeys: String, CodingKey {
case value
}

init (from decoder :Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
value = try container.decode([Int].self, forKey: .value)
}
}

But if the sample code in the question represents the entire struct it can be reduced to

struct Something: Decodable {
let value: [Int]
}

because the initializer and the CodingKeys can be inferred.

How to manually decode an array of items conforming to a custom protocol?

You can implement a concrete type, i.e AnyLogicBlock that would expose two variables let typeID: LogicBlockTypeID and let wrapped: LogicBlock

Here is a simplified example:

enum TypeID: String, Codable {
case a, b
}

protocol MyType {
var type: TypeID { get }
}

struct MyConreteTypeA: MyType {
var type: TypeID
var someVar: Int
}

struct MyConreteTypeB: MyType {
var type: TypeID
var someString: String
}

struct AnyType: MyType, Decodable {
private enum CodingKeys: String, CodingKey {
case type, someVar, someString
}

let type: TypeID
let wrapped: MyType

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

type = try container.decode(TypeID.self, forKey: .type)
switch type {
case .a:
wrapped = MyConreteTypeA(type: type, someVar: try container.decode(Int.self, forKey: .someVar))
case .b:
wrapped = MyConreteTypeB(type: type, someString: try container.decode(String.self, forKey: .someString))
}
}
}

var json = #"""
[
{ "type": "a", "someVar": 1 },
{ "type": "b", "someString": "test" }
]
"""#

let result = try! JSONDecoder().decode([AnyType].self, from: json.data(using: .utf8)!)

for item in result {
switch item.type {
case .a:
let casted = item.wrapped as! MyConreteTypeA
print("MyConreteTypeA: \(casted)")
case .b:
let casted = item.wrapped as! MyConreteTypeB
print("MyConreteTypeB: \(casted)")
}
}

Alternatively you can implement Decodable for concrete types and delegate decoding to them after determining the expected type based on type ID property.

Decoding an Array with Swift4 Codable

You decoded the weather key incorrectly. It's a top-level key so you don't need to create a subcontainer. This is enough:

self.weather = try container.decode([WeatherObj].self, forKey: .weather)

However I'd actually recommend that you create a private struct that stays very close to the JSON to ease the decoding process. Then you can pick off the pieces you want to initialize the data model:

struct Response: Codable {
var name: String
var code: Int
var temp: Double
var pressure: Int
var weather: [WeatherObj]

// This stays as close to the JSON as possible to minimize the amount of manual code
// It uses snake_case and the JSON's spelling of "cod" for "code". Since it's private,
// outside caller can never access it
private struct RawResponse: Codable {
var name: String
var cod: Int
var main: Main
var weather: [WeatherObj]

struct Main: Codable {
var temp: Double
var pressure: Int
}
}

init(from decoder: Decoder) throws {
let rawResponse = try RawResponse(from: decoder)

// Now pick the pieces you want
self.name = rawResponse.name
self.code = rawResponse.cod
self.temp = rawResponse.main.temp
self.pressure = rawResponse.main.pressure
self.weather = rawResponse.weather
}
}

Swift: decoding an array of mixed values

You could decode it using something like:

enum Either<A, B> {
case left(A)
case right(B)
}

extension Either: Codable where A: Codable, B: Codable {
struct NeitherError: Error {}

func encode(to encoder: Encoder) throws {
switch self {
case let .left(a):
try a.encode(to: encoder)
case let .right(b):
try b.encode(to: encoder)
}
}

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let a = try? container.decode(A.self) {
self = .left(a)
}
else if let b = try? container.decode(B.self) {
self = .right(b)
}
else {
throw NeitherError()
}
}
}

let input = """
[
1616662740,
"52591.9",
"52599.9",
"52591.8",
"52599.9",
"52599.1",
"0.11091626",
5
]
"""

let data = input.data(using: .utf8)!
let things = try! JSONDecoder().decode([Either<Int, String>].self, from: data)

You might want to split the Codable constraint into two extensions for Encodable and Decodable, and you might want to make a custom type which is StringifiedDouble which only decodes if the thing is a String AND is a stringified representation of a Double, e.g.

struct StringifiedDouble: Codable {
var value: Double
struct TypeError: Error {}

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let str = try container.decode(String.self)

guard let value = Double(str) else {
throw TypeError()
}

self.value = value
}
}

Decoding an Array in a Swift model (Decodable)


import Foundation

let string = """
{
"id": "xxxxxx",
"result": [
[
"client_id",
"name",
50,
"status"
]
],
"error": null
}
"""

struct Container: Codable {
let id: String
let result: [[Result]]
let error: String?
}

enum Result: Codable {
case integer(Int)
case string(String)

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(Result.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Result"))
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self)
}
}

let jsonData = string.data(using: .utf8)!
let container = try? JSONDecoder().decode(Container.self, from: jsonData)

print(container)

improved @ArinDavoodian's answer.

To read the data:

container?.result.first?.forEach { object in
switch object {
case let .integer(intValue):
print(intValue)
break
case let .string(stringValue):
print(stringValue)
break
}
}

a simple solution:

let yourInsideArray = container?.result.first!
for index in 0..<yourInsideArray.count {
let yourObjectInsideThisArray = yourInsideArray[i]
//do some
switch yourObjectInsideThisArray {
case let .integer(intValue):
print(intValue)
break
case let .string(stringValue):
print(stringValue)
break
}
}

No Data when Decoding Nested Array in Json by Swift

This code is enough:

struct PriceList: Decodable {
let success: Bool
let message: String
let response: [ResponseList]

enum CodingKeys: String, CodingKey {
case success = "IsSuccess"
case message = "Message"
case response = "ResponseData"
}
}


struct ResponseList: Decodable {
let packageID: Int
let packageName: String
let price, discountedPrice: Double
let type: String
let testPackageGroupID: Int?
let sampleType: [SampleTypeList]?

enum CodingKeys: String, CodingKey {
case packageID = "PackageId"
case packageName = "PackageName"
case price = "Price"
case discountedPrice = "DiscountedPrice"
case type = "Type"
case testPackageGroupID = "TestPackageGroupId"
case sampleType = "SampleTypeList"
}
}


struct SampleTypeList: Decodable {
let testSampleTypeID: String
let sampleName: String
let colourCode: String
enum CodingKeys: String, CodingKey {
case testSampleTypeID = "TestSampleTypeId"
case sampleName = "SampleName"
case colourCode = "ColourCode"
}
}

What I did fix, what I didn't like from your sample code:

  • Please name your variables starting with a lowercase, it's convention, and is easier to read (if everyone follow the same convention/standards).
  • Make your code compilable for us. It's not that hard, but if anyone would want to help you, it be much easier for us to just copy/paste your code and test it and not fix everything in it. You'll have better chances to get an answer. There is a Cannot assign value of type 'String' to type 'Int' because packageID is set as an Int and trying to be decoded as a String, there are missing spaces: variable= instead of variable =, etc. It's annoying to have us to fix that to be able to work.
  • You printed the JSON, that's a good point, to test it, there is no need anymore of the Web API Call, see following sample
  • You said that some values can be null, so please provide a JSON with these sample, cut if needed, see the JSON I used as sample, I checked on a JSON online validator that is was valid, and that's all. Since you that that 2 values could be null, I used all possibilities: one with none null, one with one null, one with the other null, and one with both null. Then, for each of these, I put the values as optional.
  • I removed all the init(from decoder:) as they are useless. If you decode each value with let container = try decoder.container(keyedBy:CodingKeys.self); variable = try container.decode(VariableType.self,forKey: .correspondingCodingKeyInTheEnumCase), that code is already done internally by Apple when compiling.

With sample test:

let jsonStr = """
{
"IsSuccess": true,
"Message": "Data Returned",
"ResponseData": [{
"PackageId": 1025,
"PackageName": "17 OH Progesterone",
"Price": 0.00,
"DiscountedPrice": 1.0,
"Type": "Test",
"TestPackageGroupId": 3,
"SampleTypeList": [{
"TestSampleTypeId": "50",
"SampleName": "Serum",
"ColourCode": "#FFB500"
}]
},
{
"PackageId": 1916,
"PackageName": "24 hour Albumin creatinine ratio (ACR)",
"Price": 120.00,
"DiscountedPrice": 1.0,
"Type": "Test",
"TestPackageGroupId": 3,
"SampleTypeList": null
},
{
"PackageId": 1914,
"PackageName": "24 Hour Microalbumin Creatinine Ratio",
"Price": 110.00,
"DiscountedPrice": 1.0,
"Type": "Test",
"TestPackageGroupId": null,
"SampleTypeList": [{
"TestSampleTypeId": "66",
"SampleName": "24 hrs Urine",
"ColourCode": "#212DC1"
}]
},
{
"PackageId": 1913,
"PackageName": "24 Hours Protein Creatinine Ratio (PCR) ",
"Price": 12.00,
"DiscountedPrice": 1.0,
"Type": "Test",
"TestPackageGroupId": null,
"SampleTypeList": null
}
]
}
"""

do {
let priceList = try JSONDecoder().decode(PriceList.self, from: Data(jsonStr.utf8))
print(priceList)
} catch {
print("Error while decoding: \(error)")
}


Related Topics



Leave a reply



Submit