How to define structure in iOS XCode for JSON? - ios

Being new to iOS, XCode I'm trying to create a structure to represent JSON data. However, regardless of what I try for defining "segments" (which consists of a int and an array of strings) XCode just errors out and when I try to follow suggested fixes it just generates other errors.
Anybody know how to actually define a structure for JSON that is named, e.g., not using "ANY", since all the name-value pairs and data types are known?
Example XCODE (one variation shown below, though dozens have been tried and generates errors):
struct Information: Decodable {
var entry: [Entry]
}
struct Entry: Decodable {
var section: Int
***ERROR HERE ->*** var segments: Array<var id: Int, var values: Array<String>>
}
Example JSON:
{
"entry": [
{
"section": 1,
"segments": [
{
"id": 1,
"values": ["1", "2", "3"]
},
{
"id": 2,
"values": [ "4", "5", "6" ]
}
]
},
{
"section": 2,
"segments": [
{
"id": 1,
"values": ["7", "8", "9"]
},
{
"id": 2,
"values": [ "a", "b", "c" ]
}
]
}
]
}

It's the same as on the top level: You have to create a struct for the lower level.
struct Information: Decodable {
let entry: [Entry]
}
struct Entry: Decodable {
let section: Int
let segments: [Segment]
}
struct Segment: Decodable {
let id: Int
let values: [String]
}

Related

Parse valid objects from JSON array in Swift

I have a codable struct like this
struct User: Codable {
let id: String
let registrationId: String
let firstName: String?
let lastName: String?
}
Now, the response from the server contains an array like this
[
{
"id": "1",
"registrationId": "r1",
"firstName": "Jon",
"lastName": "Doe"
},
{
"id": "2",
"registrationId": null,
"firstName": null,
"lastName": null
},
{
"id": "3",
"registrationId": null,
"firstName": null,
"lastName": null
},
{
"id": "4",
"registrationId": "r4",
"firstName": "Jon",
"lastName": "Snow"
}
]
I want to parse this as [User] but only those who have a valid(not null) registrationId. I know how to parse JSON in swift. But the problem here is because of the two invalid data in the middle the whole response will run into decoding error. But I want to parse it as an array of [User] containing valid ones(in this case first and last object).
Any hints or help is much appreciated.
1- Make registrationId an optional
let registrationId: String?
2-
let res = try JSONDecoder().decode([User].self,from:data)
let filtered = res.filter { $0.registrationId != nil }
after all, this data must come from a database or an array. By making the id parameter as the primary key, the registrationId parameter as a foreign key, and if you are working on the registrationId parameter, you can make a productive sequence or if it is on the array, you can link the method that generates the sequence for that registrationId.
Now I know how to achieve this.
struct User: Codable {
let id: String
let registrationId: String
let firstName: String?
let lastName: String?
}
struct WrappedDecodableArray<Element: Decodable>: Decodable {
let elements: [Element]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements = [Element]()
while !container.isAtEnd {
if let element = try? container.decode(Element.self) {
elements.append(element)
} else {
// move the container currentIndex forward
_ = try container.decode(Block.self)
}
}
self.elements = elements
}
private struct Block: Decodable {}
}
func testUserParsing() {
let jsonStr = """
[
{
"id": "1",
"registrationId": "r1",
"firstName": "Jon",
"lastName": "Doe"
},
{
"id": "2",
"registrationId": null,
"firstName": null,
"lastName": null
},
{
"id": "3",
"registrationId": null,
"firstName": null,
"lastName": null
},
{
"id": "4",
"registrationId": "r4",
"firstName": "Jon",
"lastName": "Snow"
}
]
"""
let jsonData = jsonStr.data(using: .utf8)!
let wrappedArray = try! JSONDecoder().decode(WrappedDecodableArray<User>.self, from: jsonData)
print(wrappedArray.elements)
}
It would have been more elegant if we could override the init(from decoder: Decoder) for Array under certain conditions like extension Array where Element == User. But looks like this is not possible. Initializer inside the extension can not override the original one and hence never get called.
So for now looks like wrapping with a struct is the only solution.

Swift Nested jSON Decode

I have problem with nested json decoding. Im getting no error but response is empty { }. Down bellow is my sample json and struct.
{
"categories": [
{
"ID": 130,
"data": [
{
"en": [
{
"title": "test"
}
],
"fr": [
{
"title": "teste"
}
]
}
],
"lifts": [
{
"ID": 104,
"data": [
{
"en": [
{
"code": "test",
"title": "test"
}
],
"fr": [
{
"code": "test",
"title": "test"
}
]
}
]
},
{
"ID": 105,
"data": [
{
"en": [
{
"code": "test",
"title": "test"
}
],
"fr": [
{
"code": "test",
"title": "test"
}
]
}
]
}
]
}
And this is my struct
struct jsonResponse : Codable {
struct Categories : Codable {
let id : Int
let data : [LanguageData]
let lifts : [Lifts]
struct LanguageData : Codable {
let en, fr : [Data]
struct Data : Codable {
let code : String?
let title : String?
}
}
struct LiftsData : Codable {
let id : Int
let data : [LanguageData]
}
}
Then Im trying to decode JSON like this:
let lifts = try JSONDecoder().decode(jsonResponse.self, from: data)
But when I print lifts, i see only empty {}. Also no error message during decoding, so have no idea what can be wrong.

get position in a api swift Codable and Model data

i am getting data from an api like this
[
{
"internData": {
"id": "abc123",
"name": "Doctor"
},
"author": "Will smith",
"description": "Is an actor",
"url": "https://www",
},
{
"internData": {
"id": "qwe900",
"name": "Constructor"
},
"author": "Edd Bett",
"description": "Is an Constructor",
"url": "https://www3",
}
]
I have my model like this
struct PersonData: Codable {
let author: String?
let description: String?
let url: String?
}
But I dont know how to define the "internData", I tried with another Model "InterData" and define id and name like the PersonData, but i get an error, i tried also with [String:Any] but i get an error for the Codable protocol
I am using
let resP = try JSONSerialization.jsonObject(with: data, options: .init()) as? [String: AnyObject]
print("resP", )
in my script of Service/Network
Thanks if somebody knows
You can't use [String:Any] type in case of Codable. you need to create an another model of InternData, which is used by PersonData.
Code:
JSON Data :
let jsonData =
"""
[
{
"internData": {
"id": "abc123",
"name": "Doctor"
},
"author": "Will smith",
"description": "Is an actor",
"url": "https://www",
},
{
"internData": {
"id": "qwe900",
"name": "Constructor"
},
"author": "Edd Bett",
"description": "Is an Constructor",
"url": "https://www3",
}
]
"""
// Models
struct PersonData: Codable {
let author: String?
let description: String?
let url: String?
let internData : InternData?
}
// New model
struct InternData : Codable {
let id : String?
let name : String?
}
// Parsing
do {
let parseRes = try JSONDecoder().decode([PersonData].self, from: Data(jsonData.utf8))
print(parseRes)
}
catch {
print(error)
}

why does swift 4 codable throw an error when parsing [duplicate]

This question already has answers here:
Using codable with value that is sometimes an Int and other times a String
(5 answers)
Closed 4 years ago.
This is my first time using the swift 4 codable protocol in an app.
I'm given a json response like this -
{
"result": [
{
"id": 1,
"heading": "Chapter 1",
"headingTextColor": "#3e9690",
"title": "Introduction: inovation for crisis",
"coverImage": "https://www.country067.com/wp-content/uploads/sites/28/2017/07/logoImage_4.jpg",
"descriptionUrl": "This is an option attribute. If not null this chapter will probably has no sections",
"sections": [
{
"id": 1,
"title": "Background",
"url": "http://api.example.com/chapter/1/section/1",
"projects": "null"
},
{
"id": 2,
"title": "Projects",
"url": null,
"projects": [
{
"id": 1,
"title": "Support to refugees",
"url": "http://api.example.com/chapter/1/project/1",
"coverImage": "https://example/wp-content/uploads/sites/28/2017/07/logoImage_4.jpg"
},
{
"id": 2,
"title": "title text",
"url": "http://api.example.com/chapter/1/project/2",
"coverImage": "https://example.com/wp-content/uploads/sites/28/2017/07/logoImage_4.jpg"
}
]
}
]
}
]
}
Using the decodable protocol, i created my models to map the response .
struct ChapterResult: Decodable {
let result: [Chapter]
}
struct Chapter: Decodable {
let id: Int
let heading: String
let headingTextColor: String
let title: String
let coverImage: String
let descriptionUrl: String
let sections: [Section]
}
struct Section: Decodable {
let id: Int
let title: String
let url: String?
let projects: [Project]?
}
struct Project: Decodable {
let id: Int
let title: String
let url: String
let coverImage: String
}
When calling the json decoder I do the following
let decoder = JSONDecoder()
let response = try decoder.decode(ChapterResult.self, from: data)
completion(.success(response.result))
which then results in the following error
failure(Optional(Error Domain=NSCocoaErrorDomain Code=4864 "Expected
to decode Array but found a string/data instead."
UserInfo={NSCodingPath=(
"CodingKeys(stringValue: \"result\", intValue: nil)",
"_JSONKey(stringValue: \"Index 0\", intValue: 0)",
"CodingKeys(stringValue: \"sections\", intValue: nil)",
"_JSONKey(stringValue: \"Index 0\", intValue: 0)",
"CodingKeys(stringValue: \"projects\", intValue: nil)"
It says result[0].sections[0].projects has a TypeMissMatch error.
So this "projects": "null" should be an array but it's string.
Read the error:
Expected to decode Array but found a string/data instead
In your JSON file projects have String or Array:
"projects": "null"
"projects": [
{
"id": 1
To fix the issue "null" should be null. If you can't edit JSON file you should write custom initializer.
see, Accroding to your struct
struct Section: Decodable {
let id: Int
let title: String
let url: String?
let projects: [Project]?
}
projects is a array of type project.
{
"id": 1,
"title": "Background",
"url": "http://api.example.com/chapter/1/section/1",
"projects": "null"
}
But at 0th index of sections, the value of projects key is "null" which is a string, so that's why you are getting an error.
Now read carefully your error it contains "Expected to decode Array but found a string/data instead."

How to use a struct with a library in a tableview

I have created a struct with a library and have initialised that struct in another struct. Now I want to use the resulting struct in a Table View. However, the struct currently doesn't work properly. I've tried to find out why, but seem to be stuck.
It looks like the issue is that the library doesn't get translated properly to the struct. For in instance, when I do a count on an instance of the struct, using the following code:
var personalDetailsStructExtra: [PersonalDetailsStruct] = []
personalDetailsStructExtra.count
It returns 0, while it should be 5 (See code below, there are 5 entries into the dictionary):
struct PersonalDetailsStructLibrary {
let library = [
[
"title": "Country",
"icon": "country.pdf",
"questions": ["Belgium", "France", "Germany", "Netherlands", "Sweden", "UK", "USA"]
],
[
"title": "Age",
"icon": "age.pdf",
"questions": ["1", "2", "3", "4", "5", "6", "7"]
],
[
"title": "Gender",
"icon": "gender.pdf",
"questions": ["Male", "Female", "Other"]
],
[
"title": "Height",
"icon": "height.pdf",
"questions": ["1", "2", "3", "4", "5", "6", "7"]
],
[
"title": "Weight",
"icon": "weight.pdf",
"questions": ["1", "2", "3", "4", "5", "6", "7"]
],
] }
And
struct PersonalDetailsStruct {
var title: String?
var icon: UIImage?
var questions: [String] = []
init(index: Int) {
let personalDetailsStructLibrary = PersonalDetailsStructLibrary().library
let personalDetailsDictionary = personalDetailsStructLibrary[index]
title = personalDetailsDictionary["title"] as! String!
let iconNamePD = personalDetailsDictionary["icon"] as! String!
icon = UIImage(named: iconNamePD!)
questions += personalDetailsDictionary["artists"] as! [String]
} }
As you can see in the code I want use the struct to fill up a label (title), image (icon) and UITextView with UIPickerView (questions) in my table view.
Since it doesn't work, I'm looking for either:
A: Feedback on how to make this code work in a tableview
B: Whether I should use another method to populate the dynamic cells in my tableview
You have to initialize the personalDetailsStructExtra array, only then you would see the required count.
var personalDetailsStructExtra = [PersonalDetailsStruct]()
PersonalDetailsStructLibrary().library.count
let count = PersonalDetailsStructLibrary().library.count
for i in 0..<count {
personalDetailsStructExtra.append(PersonalDetailsStruct(index: i))
}
personalDetailsStructExtra.count // 5
A better option is to use the Library struct to construct and maintain all model objects.
struct PersonalDetailDataSource {
let library = [
[
"title": "Country",
"icon": "country.pdf",
"questions": ["Belgium", "France", "Germany", "Netherlands", "Sweden", "UK", "USA"]
],
[
"title": "Age",
"icon": "age.pdf",
"questions": ["1", "2", "3", "4", "5", "6", "7"]
],
[
"title": "Gender",
"icon": "gender.pdf",
"questions": ["Male", "Female", "Other"]
],
[
"title": "Height",
"icon": "height.pdf",
"questions": ["1", "2", "3", "4", "5", "6", "7"]
],
[
"title": "Weight",
"icon": "weight.pdf",
"questions": ["1", "2", "3", "4", "5", "6", "7"]
],
]
var personalDetails = [PersonalDetail]()
init() {
loadData()
}
mutating private func loadData() {
for i in 0..<library.count {
let personalDetailsDictionary = library[i]
let title = personalDetailsDictionary["title"] as! String!
let iconName = personalDetailsDictionary["icon"] as! String!
let questions = personalDetailsDictionary["questions"] as! [String]
personalDetails.append(PersonalDetail(title: title, iconName: iconName, questions: questions))
}
}
}
struct PersonalDetail {
var title: String?
var icon: UIImage?
var questions: [String] = []
init(title: String, iconName: String, questions: [String]) {
self.title = title
if let icon = UIImage(named: iconName) {
self.icon = icon
}
self.questions = questions
}
}
PersonalDetailDataSource().personalDetails.count // count: 5

Resources