How to parse this JSON format in swift - arrays

I have this JSON format:
{
"version":"7.0.19",
"fields": ["ID","pm","age","pm_0","pm_1","pm_2","pm_3","pm_4","pm_5","pm_6","conf","pm1","pm_10","p1","p2","p3","p4","p5","p6","Humidity","Temperature","Pressure","Elevation","Type","Label","Lat","Lon","Icon","isOwner","Flags","Voc","Ozone1","Adc","CH"],
"data":[[20,0.0,1,0.0,0.0,0.0,0.0,0.0,0.0,0.0,97,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,null,null,null,1413,0,"Oakdale",40.603077,-111.83612,0,0,0,null,null,0.01,1]],
"count":11880
}
but I cannot work out how to use a Codable protocol to parse the json response.
this would be my desired model.
struct Point: Codable {
let pm2: String?
let latitude, longitude: Double?
let temp: String?
let iD: String?
enum CodingKeys: String, CodingKey {
case pm2 = "pm", temp = "Temperature", iD = "ID", latitude = "Lat", longitude = "Lon"
}
}
Here is a URL to the json
https://webbfoot.com/dataa.json

You can use Codable to parse this:
struct Response: Decodable {
let version: String
let fields: [String]
let data: [[QuantumValue?]]
let count: Int
}
enter code here
enum QuantumValue: Decodable {
case float(Float), string(String)
init(from decoder: Decoder) throws {
if let int = try? decoder.singleValueContainer().decode(Float.self) {
self = .float(float)
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
throw QuantumError.missingValue
}
enum QuantumError:Error {
case missingValue
}
}
QuantumValue will handle both Float and String and ? will handle the null part.

This one is tricky and requires manual decoding. The principle would be to define a mapping between the fields you expect to decode and the properties of your object, then depending on the type of the property (String, Double, etc...), attempt to decode the data.
Second, since you have an array of points, you need some kind of container object to hold the array, for example:
struct Points {
var data: [Point] = []
}
First, some of your model properties don't match the type in the data, e.g. iD is a String, but the data has an Int. For simplicity, I'll redefine your model to match the data
struct Point {
var pm2: Int? = nil
var latitude: Double? = nil
var longitude: Double? = nil
var temp: Int? = nil
var iD: Int? = nil
}
Now, write the manual decoder for the parent container Points:
extension Points: Decodable {
static let mapping: [String: PartialKeyPath<Point>] = [
"pm": \Point.pm2,
"Lat": \Point.latitude,
"Lon": \Point.longitude,
"Temperature": \Point.temp,
"ID": \Point.iD
]
enum CodingKeys: CodingKey { case fields, data }
private struct Dummy: Decodable {} // see below why
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let fields = try container.decode([String].self, forKey: .fields)
var data = try container.nestedUnkeyedContainer(forKey: .data)
while !data.isAtEnd {
var row = try data.nestedUnkeyedContainer()
var point = Point()
for field in fields {
let keyPath = Points.mapping[field]
switch keyPath {
case let kp as WritableKeyPath<Point, String?>:
point[keyPath: kp] = try row.decodeIfPresent(String.self)
case let kp as WritableKeyPath<Point, Int?>:
point[keyPath: kp] = try row.decodeIfPresent(Int.self)
case let kp as WritableKeyPath<Point, Double?>:
point[keyPath: kp] = try row.decodeIfPresent(Double.self)
default:
// this is a hack to skip this value
let _ = try? row.decode(Dummy.self)
}
}
self.data.append(point)
}
}
}
Once you have that, you can decode the JSON like so:
let points = try JSONDecoder().decode(Points.self, from: jsonData)
let firstPoint = points.data[0]

Related

JSONObject of JSONArray with String values in Swift5

I need to create a JSON Object containing two JSON Arrays with a specific structure.
Here is the result I would need:
{"config": [{"battery_state" = "3.12","max_hum" = "33","mode" = "mode"}], "alarms": [{"1" = "12345678"}, {"2" = "22334455"}]}
I tried to use NSMutableDictionary and Arrays but the result is not what I'm expecting
let jsonConfigObject: NSMutableDictionary = NSMutableDictionary()
jsonConfigObject.setValue("33" as String, forKey: "max_hum" as String)
jsonConfigObject.setValue("3.12" as String, forKey: "battery_state" as String)
jsonConfigObject.setValue("mode" as String, forKey: "mode" as String)
let arrayConfig = [jsonConfigObject]
var jsonAlarmObject: NSMutableDictionary = NSMutableDictionary()
jsonAlarmObject.setValue("12345678" as String, forKey: "1" as String)
var arrayAlarms = [jsonAlarmObject]
jsonAlarmObject = NSMutableDictionary()
jsonAlarmObject.setValue("22334455" as String, forKey: "2" as String)
arrayAlarms.append(jsonAlarmObject)
let array = [["config" : arrayConfig], ["alarms" : arrayAlarms]]
The result is the following:
[["config": [{"battery_state" = "3.12";"max_hum" = 33;mode = mode;}]], ["alarms": [{1 = 12345678;}, {2 = 22334455;}]]]
any idea how I can get such JSON Structure ?
EDIT 1
I tried to use the following:
struct Config: Codable {
let battery_state, max_hum, mode: String
enum CodingKeys: String, CodingKey {
case battery_state = "battery_state"
case max_hum = "max_hum"
case mode = "mode"
}
}
var conf = Config(battery_state: "0", max_hum: "5", mode: "A")
var alarms = [[String:String]]()
alarms.append(["1":"123456"])
alarms.append(["2":"123456"])
alarms.append(["nb_alarms":String(Tag.sharedInstance.nb_alarms)])
but it gives me
{"config":[{"current_hum":"56","period":"2","battery_level":"2.9"],"alarms":[{"1":"123456"},{"2":"123456"},{"nb_alarms":"2"}]}
but I would need:
{"config":[{"current_hum":"56","period":"2","battery_level":"2.9"],"alarms":[{"1":"123456","2":"123456","nb_alarms":"2"]}
I need to change my alarm String:String but as I have plenty of data to be added in it I don't know the format of alarm I need to use...
Try this approach, using struct models (MyObject and Config) to ...create a JSON Object containing two JSON Arrays with a specific structure.
This example code shows how to code/decode your object from/to json.
struct MyObject: Codable {
let config: [Config]
let alarms: [[String: String]]
}
struct Config: Codable {
let batteryState, maxHum, mode: String
enum CodingKeys: String, CodingKey {
case batteryState = "battery_state"
case maxHum = "max_hum"
case mode
}
}
// the (corrected) json data
let json = """
{"config": [{"battery_state": "3.12","max_hum": "33","mode" : "mode"}], "alarms": [{"1": "12345678"}, {"2": "22334455"}]}
"""
// initial empty myObject
var myObject = MyObject(config: [], alarms: [])
// json string to MyObject
if let data = json.data(using: .utf8) {
do {
myObject = try JSONDecoder().decode(MyObject.self, from: data)
print("---> myObject: \(myObject)")
} catch {
print("decode error: \(error)")
}
}
// MyObject to json string
do {
let encodedData = try JSONEncoder().encode(myObject)
let theJson = String(data: encodedData, encoding: .utf8)
print("---> theJson: \(theJson!)")
} catch {
print(error)
}
// usage
myObject.config.forEach{ conf in
print("---> conf.batteryState: \(conf.batteryState)")
print("---> conf.maxHum: \(conf.maxHum)")
}
myObject.alarms.forEach{ alarm in
print("---> alarm: \(alarm)")
}

Decoding JSON array with codable - Swift 5

I would like to decode the json data in the following link
https://stats.nba.com/stats/assistleaders?LeagueID=00&PerMode=Totals&PlayerOrTeam=Team&Season=2019-20&SeasonType=Regular+Season
However, the rowSet data is an set of json array in inside an array. How to set up the codable struct to decode this data? I am able to set up the following struct to decode the other data.
import Foundation
struct leagueLeader: Codable {
var resource: String
var parameters: parameters
}
struct parameters: Codable {
var LeagueID: String
var PerMode: String
var StatCategory: String
var Season: String
var SeasonType: String
}
struct resultSet: Codable {
var name: String
var headers: [String]
}
This JSON structure is very unusual. It's a kind of comma separated values (CSV) which cannot be decoded implicitly into a struct.
A solution is to decode the array manually
struct RowSet: Decodable {
let rank, teamID : Int
let teamAbbreviation, teamName : String
let ast : Int
init(from decoder: Decoder) throws {
var arrayContainer = try decoder.unkeyedContainer()
guard arrayContainer.count == 5 else { throw DecodingError.dataCorruptedError(in: arrayContainer, debugDescription: "The array must contain 5 items") }
rank = try arrayContainer.decode(Int.self)
teamID = try arrayContainer.decode(Int.self)
teamAbbreviation = try arrayContainer.decode(String.self)
teamName = try arrayContainer.decode(String.self)
ast = try arrayContainer.decode(Int.self)
}
}
As mentioned in the comments please conform to the naming convention: structs, classes and enums start with a uppercase letter, variables, properties, functions and enum cases start with lowercase letter.
The hard part is dealing with rowSet that supposed to contains multiple data types
{
"rowSet": [
[
1,
1610612756,
"PHX",
"Phoenix Suns",
1987
]
]
}
The solution is to declare an enum that each case has an associated value representing a codable data type
enum RowSet: Codable, Equatable {
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(RowSet.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for RowSet"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
Full of model:
import Foundation
struct Leader: Codable, Equatable {
let resource: String?
let parameters: Parameters?
let resultSets: [ResultSet]?
}
struct Parameters: Codable, Equatable {
let leagueId, season, seasonType, perMode: String?
let playerOrTeam: String?
enum CodingKeys: String, CodingKey {
case leagueId = "LeagueID"
case season = "Season"
case seasonType = "SeasonType"
case perMode = "PerMode"
case playerOrTeam = "PlayerOrTeam"
}
}
struct ResultSet: Codable, Equatable {
let name: String?
let headers: [String]?
let rowSet: [[RowSet]]?
}
enum RowSet: Codable, Equatable {
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(RowSet.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for RowSet"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}

typeMismatch Error While parsing JSON data in Swift 4.2

Type mismatch Error - expected to decode Array but found dictionary.
I am using local json file embedded in the code. Json file contains the information about packages. I need to pick values and show them accordingly into tableview controller. Please identify what am I doing wrong in codable model or in a code. JSON Parsing not properly handled.
File Format:
{
"packages": [
{
"name": "Platinum Maksi 6 GB",
"desc": "Zengin içerikli Platinum Maksi Paketi ile Turkcell Uygulamalarının keyfini sürün!",
"subscriptionType": "monthly",
"didUseBefore": true,
"benefits": [
"TV+",
"Fizy",
"BiP",
"lifebox",
"Platinum",
"Dergilik"
],
"price": 109.90,
"tariff": {
"data": "6144",
"talk": "2000",
"sms": "100"
},
"availableUntil": "1558131150"
}
]
}
Models:
Base Model
struct Base : Codable {
let packages : [Package]?
enum CodingKeys: String, CodingKey {
case packages = "packages"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
packages = try values.decodeIfPresent([Package].self, forKey: .packages)
}
2) Package Model:
struct Package : Codable {
let availableUntil : String?
let benefits : String?
let desc : String?
let didUseBefore : Bool?
let name : String?
let price : Int?
let subscriptionType : String?
let tariff : Tariff?
enum CodingKeys: String, CodingKey {
case availableUntil = "availableUntil"
case benefits = "benefits"
case desc = "desc"
case didUseBefore = "didUseBefore"
case name = "name"
case price = "price"
case subscriptionType = "subscriptionType"
case tariff = "tariff"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
availableUntil = try values.decodeIfPresent(String.self, forKey: .availableUntil)
benefits = try values.decodeIfPresent(String.self, forKey: .benefits)
desc = try values.decodeIfPresent(String.self, forKey: .desc)
didUseBefore = try values.decodeIfPresent(Bool.self, forKey: .didUseBefore)
name = try values.decodeIfPresent(String.self, forKey: .name)
price = try values.decodeIfPresent(Int.self, forKey: .price)
subscriptionType = try values.decodeIfPresent(String.self, forKey: .subscriptionType)
tariff = try Tariff(from: decoder)
}
}
Tariff Model:
struct Tariff : Codable {
let data : String?
let sms : String?
let talk : String?
enum CodingKeys: String, CodingKey {
case data = "data"
case sms = "sms"
case talk = "talk"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = try values.decodeIfPresent(String.self, forKey: .data)
sms = try values.decodeIfPresent(String.self, forKey: .sms)
talk = try values.decodeIfPresent(String.self, forKey: .talk)
}
}
My code:
var pack = [Package]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = Bundle.main.url(forResource: "packageList", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
pack = try JSONDecoder().decode([Package].self, from: data)
print(pack)
} catch {
print(error)
}
You are decoding the wrong object. You have to decode always the root object of the JSON which is Base.
Get the Package array with base.packages
let base = try JSONDecoder().decode(Base.self, from: data)
pack = base.packages
Now you will get two other type mismatch errors, change the type of benefits to [String] and price to Double.
You can reduce your structs dramatically: Remove all CodingKeys and all initializers and declare all struct members as non-optional (remove the question marks).
Edit:
To decode availableUntil as Date declare it as Date
let availableUntil: Date
and add a custom date decoding strategy because the value is a string (It would be easier if the value was Int)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom{ decoder -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
guard let interval = TimeInterval(dateStr) else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Date string cannot be converted to TimeInterval") }
return Date(timeIntervalSince1970: interval)
}
let data = try Data(contentsOf: url)
let base = try decoder.decode(Base.self, from: data)
pack = base.packages

How can I convert Json into 2D array?

If I got a Json like this:
{ "i": [ "0", [123]] }
Is there any possible way can decode the 2D array above?
class ModelA: Codable{
var i: [String]?
var temp: [Any] = []
enum CodingKeys: String, CodingKey {
case i = "i"
}
required init(from decoder: Decoder) throws {
let value = try decoder.container(keyedBy: CodingKeys.self)
temp = try value.decode([Any].self, forKey: .i)
}
}
Usage:
public func printJsonData(){
let jsonData: Data = """
{
"i": [ "0", [123]]
}
""".data(using: .utf8)!
if let model = try? JSONDecoder().decode(ModelA.self, from: jsonData){
print(model.temp)
}else{
print("no data")
}
}
I have tried that an array [Any] works successfully here,
but can't find any method to convert in 2D array.
If someone knows how to solve this problem, or knows this is impossible in Swift4.2, please tell me. Thanks!
If you know the possible data types for the array value, perhaps you can try using the possible value (in this case String and [Int]) represented by enum instead of Any.
e.g:
enum ArrayIntOrString: Decodable {
case string(String)
case arrayOfInt([Int])
init(from decoder: Decoder) throws {
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
if let arrayOfInt = try? decoder.singleValueContainer().decode([Int].self) {
self = .arrayOfInt(arrayOfInt)
return
}
throw ArrayIntOrStringError.arrayIntOrStringNotFound
}
enum ArrayIntOrStringError: Error {
case arrayIntOrStringNotFound
}
}
And declare it in your model:
class ModelA: Decodable {
var i: [ArrayIntOrString]?
enum CodingKeys: String, CodingKey {
case i = "i"
}
}
usage
public func printJsonData() {
let jsonData: Data = """
{
"i": [ "0", [123]]
}
""".data(using: .utf8)!
do {
let model = try JSONDecoder().decode(ModelA.self, from: jsonData)
print(model.i)
} catch let err {
print("no data \(err)")
}
}

Swift JSONDecode decoding arrays fails if single element decoding fails

While using Swift4 and Codable protocols I got the following problem - it looks like there is no way to allow JSONDecoder to skip elements in an array.
For example, I have the following JSON:
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
And a Codable struct:
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
When decoding this json
let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
Resulting products is empty. Which is to be expected, due to the fact that the second object in JSON has no "points" key, while points is not optional in GroceryProduct struct.
Question is how can I allow JSONDecoder to "skip" invalid object?
One option is to use a wrapper type that attempts to decode a given value; storing nil if unsuccessful:
struct FailableDecodable<Base : Decodable> : Decodable {
let base: Base?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.base = try? container.decode(Base.self)
}
}
We can then decode an array of these, with your GroceryProduct filling in the Base placeholder:
import Foundation
let json = """
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
""".data(using: .utf8)!
struct GroceryProduct : Codable {
var name: String
var points: Int
var description: String?
}
let products = try JSONDecoder()
.decode([FailableDecodable<GroceryProduct>].self, from: json)
.compactMap { $0.base } // .flatMap in Swift 4.0
print(products)
// [
// GroceryProduct(
// name: "Banana", points: 200,
// description: Optional("A banana grown in Ecuador.")
// )
// ]
We're then using .compactMap { $0.base } to filter out nil elements (those that threw an error on decoding).
This will create an intermediate array of [FailableDecodable<GroceryProduct>], which shouldn't be an issue; however if you wish to avoid it, you could always create another wrapper type that decodes and unwraps each element from an unkeyed container:
struct FailableCodableArray<Element : Codable> : Codable {
var elements: [Element]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements = [Element]()
if let count = container.count {
elements.reserveCapacity(count)
}
while !container.isAtEnd {
if let element = try container
.decode(FailableDecodable<Element>.self).base {
elements.append(element)
}
}
self.elements = elements
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(elements)
}
}
You would then decode as:
let products = try JSONDecoder()
.decode(FailableCodableArray<GroceryProduct>.self, from: json)
.elements
print(products)
// [
// GroceryProduct(
// name: "Banana", points: 200,
// description: Optional("A banana grown in Ecuador.")
// )
// ]
I would create a new type Throwable, which can wrap any type conforming to Decodable:
enum Throwable<T: Decodable>: Decodable {
case success(T)
case failure(Error)
init(from decoder: Decoder) throws {
do {
let decoded = try T(from: decoder)
self = .success(decoded)
} catch let error {
self = .failure(error)
}
}
}
For decoding an array of GroceryProduct (or any other Collection):
let decoder = JSONDecoder()
let throwables = try decoder.decode([Throwable<GroceryProduct>].self, from: json)
let products = throwables.compactMap { $0.value }
where value is a computed property introduced in an extension on Throwable:
extension Throwable {
var value: T? {
switch self {
case .failure(_):
return nil
case .success(let value):
return value
}
}
}
I would opt for using a enum wrapper type (over a Struct) because it may be useful to keep track of the errors that are thrown as well as their indices.
Swift 5
For Swift 5 Consider using the Result enum e.g.
struct Throwable<T: Decodable>: Decodable {
let result: Result<T, Error>
init(from decoder: Decoder) throws {
result = Result(catching: { try T(from: decoder) })
}
}
To unwrap the decoded value use the get() method on the result property:
let products = throwables.compactMap { try? $0.result.get() }
The problem is that when iterating over a container, the container.currentIndex isn’t incremented so you can try to decode again with a different type.
Because the currentIndex is read only, a solution is to increment it yourself successfully decoding a dummy. I took #Hamish solution, and wrote a wrapper with a custom init.
This problem is a current Swift bug: https://bugs.swift.org/browse/SR-5953
The solution posted here is a workaround in one of the comments.
I like this option because I’m parsing a bunch of models the same way on a network client, and I wanted the solution to be local to one of the objects. That is, I still want the others to be discarded.
I explain better in my github https://github.com/phynet/Lossy-array-decode-swift4
import Foundation
let json = """
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
""".data(using: .utf8)!
private struct DummyCodable: Codable {}
struct Groceries: Codable
{
var groceries: [GroceryProduct]
init(from decoder: Decoder) throws {
var groceries = [GroceryProduct]()
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let route = try? container.decode(GroceryProduct.self) {
groceries.append(route)
} else {
_ = try? container.decode(DummyCodable.self) // <-- TRICK
}
}
self.groceries = groceries
}
}
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let products = try JSONDecoder().decode(Groceries.self, from: json)
print(products)
There are two options:
Declare all members of the struct as optional whose keys can be missing
struct GroceryProduct: Codable {
var name: String
var points : Int?
var description: String?
}
Write a custom initializer to assign default values in the nil case.
struct GroceryProduct: Codable {
var name: String
var points : Int
var description: String
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
points = try values.decodeIfPresent(Int.self, forKey: .points) ?? 0
description = try values.decodeIfPresent(String.self, forKey: .description) ?? ""
}
}
A solution made possible by Swift 5.1, using the property wrapper:
#propertyWrapper
struct IgnoreFailure<Value: Decodable>: Decodable {
var wrappedValue: [Value] = []
private struct _None: Decodable {}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let decoded = try? container.decode(Value.self) {
wrappedValue.append(decoded)
}
else {
// item is silently ignored.
try? container.decode(_None.self)
}
}
}
}
And then the usage:
let json = """
{
"products": [
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
}
""".data(using: .utf8)!
struct GroceryProduct: Decodable {
var name: String
var points: Int
var description: String?
}
struct ProductResponse: Decodable {
#IgnoreFailure
var products: [GroceryProduct]
}
let response = try! JSONDecoder().decode(ProductResponse.self, from: json)
print(response.products) // Only contains banana.
Note: The property wrapper things will only works if the response can be wrapped in a struct (i.e: not a top level array).
In that case, you can still wrap it manually (with a typealias for better readability):
typealias ArrayIgnoringFailure<Value: Decodable> = IgnoreFailure<Value>
let response = try! JSONDecoder().decode(ArrayIgnoringFailure<GroceryProduct>.self, from: json)
print(response.wrappedValue) // Only contains banana.
Ive put #sophy-swicz solution, with some modifications, into an easy to use extension
fileprivate struct DummyCodable: Codable {}
extension UnkeyedDecodingContainer {
public mutating func decodeArray<T>(_ type: T.Type) throws -> [T] where T : Decodable {
var array = [T]()
while !self.isAtEnd {
do {
let item = try self.decode(T.self)
array.append(item)
} catch let error {
print("error: \(error)")
// hack to increment currentIndex
_ = try self.decode(DummyCodable.self)
}
}
return array
}
}
extension KeyedDecodingContainerProtocol {
public func decodeArray<T>(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T : Decodable {
var unkeyedContainer = try self.nestedUnkeyedContainer(forKey: key)
return try unkeyedContainer.decodeArray(type)
}
}
Just call it like this
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.items = try container.decodeArray(ItemType.self, forKey: . items)
}
For the example above:
let json = """
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
""".data(using: .utf8)!
struct Groceries: Codable
{
var groceries: [GroceryProduct]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
groceries = try container.decodeArray(GroceryProduct.self)
}
}
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let products = try JSONDecoder().decode(Groceries.self, from: json)
print(products)
Instead, You can also do like this:
struct GroceryProduct: Decodable {
var name: String
var points: Int
var description: String?
}'
and then in while getting it:
'let groceryList = try JSONDecoder().decode(Array<GroceryProduct>.self, from: responseData)'
Unfortunately Swift 4 API doesn't have failable initializer for init(from: Decoder).
Only one solution that I see is implementing custom decoding, giving default value for optional fields and possible filter with needed data:
struct GroceryProduct: Codable {
let name: String
let points: Int?
let description: String
private enum CodingKeys: String, CodingKey {
case name, points, description
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
points = try? container.decode(Int.self, forKey: .points)
description = (try? container.decode(String.self, forKey: .description)) ?? "No description"
}
}
// for test
let dict = [["name": "Banana", "points": 100], ["name": "Nut", "description": "Woof"]]
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []) {
let decoder = JSONDecoder()
let result = try? decoder.decode([GroceryProduct].self, from: data)
print("rawResult: \(result)")
let clearedResult = result?.filter { $0.points != nil }
print("clearedResult: \(clearedResult)")
}
I improved on #Hamish's for the case, that you want this behaviour for all arrays:
private struct OptionalContainer<Base: Codable>: Codable {
let base: Base?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
base = try? container.decode(Base.self)
}
}
private struct OptionalArray<Base: Codable>: Codable {
let result: [Base]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let tmp = try container.decode([OptionalContainer<Base>].self)
result = tmp.compactMap { $0.base }
}
}
extension Array where Element: Codable {
init(from decoder: Decoder) throws {
let optionalArray = try OptionalArray<Element>(from: decoder)
self = optionalArray.result
}
}
Swift 5
Inspired with previous answers I decode inside Result enum extension.
What do you think about it?
extension Result: Decodable where Success: Decodable, Failure == DecodingError {
public init(from decoder: Decoder) throws {
let container: SingleValueDecodingContainer = try decoder.singleValueContainer()
do {
self = .success(try container.decode(Success.self))
} catch {
if let decodingError = error as? DecodingError {
self = .failure(decodingError)
} else {
self = .failure(DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: error.localizedDescription)))
}
}
}
}
Usage
let listResult = try? JSONDecoder().decode([Result<SomeObject, DecodingError>].self, from: ##YOUR DATA##)
let list: [SomeObject] = listResult.compactMap {try? $0.get()}
#Hamish's answer is great. However, you can reduce FailableCodableArray to:
struct FailableCodableArray<Element : Codable> : Codable {
var elements: [Element]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let elements = try container.decode([FailableDecodable<Element>].self)
self.elements = elements.compactMap { $0.wrapped }
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(elements)
}
}
I had a similar issue recently, but slightly different.
struct Person: Codable {
var name: String
var age: Int
var description: String?
var friendnamesArray:[String]?
}
In this case, if one of the element in friendnamesArray is nil, the whole object is nil while decoding.
And the right way to handle this edge case is to declare the string array[String] as array of optional strings[String?] as below,
struct Person: Codable {
var name: String
var age: Int
var description: String?
var friendnamesArray:[String?]?
}
You made the description optional, you should also make the points field optional if there is a chance it could be nil, such as this:
struct GroceryProduct: Codable {
var name: String
var points: Int?
var description: String?
}
Just make sure you safe-unwrap it however you see fit for it's use. I'm guessing nil points == 0 in the actual use case so an example could be:
let products = try JSONDecoder().decode([GroceryProduct].self, from: json)
for product in products {
let name = product.name
let points = product.points ?? 0
let description = product.description ?? ""
ProductView(name, points, description)
}
or in-line:
let products = try JSONDecoder().decode([GroceryProduct].self, from: json)
for product in products {
ProductView(product.name, product.points ?? 0, product.description ?? "")
}
I come up with this KeyedDecodingContainer.safelyDecodeArray that provides a simple interface:
extension KeyedDecodingContainer {
/// The sole purpose of this `EmptyDecodable` is allowing decoder to skip an element that cannot be decoded.
private struct EmptyDecodable: Decodable {}
/// Return successfully decoded elements even if some of the element fails to decode.
func safelyDecodeArray<T: Decodable>(of type: T.Type, forKey key: KeyedDecodingContainer.Key) -> [T] {
guard var container = try? nestedUnkeyedContainer(forKey: key) else {
return []
}
var elements = [T]()
elements.reserveCapacity(container.count ?? 0)
while !container.isAtEnd {
/*
Note:
When decoding an element fails, the decoder does not move on the next element upon failure, so that we can retry the same element again
by other means. However, this behavior potentially keeps `while !container.isAtEnd` looping forever, and Apple does not offer a `.skipFailable`
decoder option yet. As a result, `catch` needs to manually skip the failed element by decoding it into an `EmptyDecodable` that always succeed.
See the Swift ticket https://bugs.swift.org/browse/SR-5953.
*/
do {
elements.append(try container.decode(T.self))
} catch {
if let decodingError = error as? DecodingError {
Logger.error("\(#function): skipping one element: \(decodingError)")
} else {
Logger.error("\(#function): skipping one element: \(error)")
}
_ = try? container.decode(EmptyDecodable.self) // skip the current element by decoding it into an empty `Decodable`
}
}
return elements
}
}
The potentially infinite loop while !container.isAtEnd is a concern, and it's addressed by using EmptyDecodable.
A much simpler attempt:
Why don't you declare points as optional or make the array contain optional elements
let products = [GroceryProduct?]
Features:
Simple use. One line in Decodable instance: let array: CompactDecodableArray<Int>
Is decoded with standard mapping mechanism: JSONDecoder().decode(Model.self, from: data)
skips incorrect elements (returns array with only successful mapped elements)
Details
Xcode 12.1 (12A7403)
Swift 5.3
Solution
class CompactDecodableArray<Element>: Decodable where Element: Decodable {
private(set) var elements = [Element]()
required init(from decoder: Decoder) throws {
guard var unkeyedContainer = try? decoder.unkeyedContainer() else { return }
while !unkeyedContainer.isAtEnd {
if let value = try? unkeyedContainer.decode(Element.self) {
elements.append(value)
} else {
unkeyedContainer.skip()
}
}
}
}
// https://forums.swift.org/t/pitch-unkeyeddecodingcontainer-movenext-to-skip-items-in-deserialization/22151/17
struct Empty: Decodable { }
extension UnkeyedDecodingContainer {
mutating func skip() { _ = try? decode(Empty.self) }
}
Usage
struct Model2: Decodable {
let num: Int
let str: String
}
struct Model: Decodable {
let num: Int
let str: String
let array1: CompactDecodableArray<Int>
let array2: CompactDecodableArray<Int>?
let array4: CompactDecodableArray<Model2>
}
let dictionary: [String : Any] = ["num": 1, "str": "blablabla",
"array1": [1,2,3],
"array3": [1,nil,3],
"array4": [["num": 1, "str": "a"], ["num": 2]]
]
let data = try! JSONSerialization.data(withJSONObject: dictionary)
let object = try JSONDecoder().decode(Model.self, from: data)
print("1. \(object.array1.elements)")
print("2. \(object.array2?.elements)")
print("3. \(object.array4.elements)")
Console
1. [1, 2, 3]
2. nil
3. [__lldb_expr_25.Model2(num: 1, str: "a")]

Resources