How to parse JSON Array Objects with SwiftyJSON? - ios

I have a JSON file which contains list of array objects. I'm unable to parse that json file
I have tried this code but it didn't work
if let tempResult = json[0]["sno"].string{
print("Temp Result is \(tempResult)")
}
else {
print(json[0]["sno"].error!)
print("Temp Result didn't worked")
}
Here is my JSON File
[
{
"sno": "21",
"title": "title 1",
"tableid": "table 1"
},
{
"sno": "19",
"title": "title 222",
"tableid": "table 222"
},
{
"sno": "3",
"title": "title 333",
"tableid": "table 333"
}
]

Actually it would be better to define a struct for object in Array.
public struct Item {
// MARK: Declaration for string constants to be used to decode and also serialize.
private struct SerializationKeys {
static let sno = "sno"
static let title = "title"
static let tableid = "tableid"
}
// MARK: Properties
public var sno: String?
public var title: String?
public var tableid: String?
// MARK: SwiftyJSON Initializers
/// Initiates the instance based on the object.
///
/// - parameter object: The object of either Dictionary or Array kind that was passed.
/// - returns: An initialized instance of the class.
public init(object: Any) {
self.init(json: JSON(object))
}
/// Initiates the instance based on the JSON that was passed.
///
/// - parameter json: JSON object from SwiftyJSON.
public init(json: JSON) {
sno = json[SerializationKeys.sno].string
title = json[SerializationKeys.title].string
tableid = json[SerializationKeys.tableid].string
}
}
And you need to map your array of JSON to Item objects.
var items = [Item]()
if let arrayJSON = json.array
items = arrayJSON.map({return Item(json: $0)})
}

Ditch SwiftyJSON and use Swift's built-in Codable with model objects instead:
typealias Response = [ResponseElement]
struct ResponseElement: Codable {
let sno, title, tableid: String
}
do {
let response = try JSONDecoder().decode(Response.self, from: data)
}
catch {
print(error)
}
where data is the raw JSON data you got from your API.

Related

how to write json objects one by one in to a json object in swift

I need to create an answer JSON with nested objects
I tried to use mappable but it won't solve the problem
I need to create this kind of array
{
"interactionId":"daade6b6adcd8b063a355e28bc1f1341",
"futureSurveyAnswers":[
{
"type":"imagepicker",
"qcode":"vxo20zeezo",
"values":[
{
"value":"lion"
}
]
},
{
"type":"radiogroup",
"qcode":"s4ep4s0shf",
"values":[
{
"value":"item1"
}
]
},
{
"type":"checkbox",
"qcode":"76k5cpnpki",
"values":[
{
"value":"item1"
},
{
"value":"item2"
}
]
}
],
"originalResultArray":"{\"question2\":\"item1\",\"question3\":[\"item1\",\"item2\"],\"question1\":\"lion\"}"
}
Currently probably the best solution to use is Codable protocol. There are many examples and tutorials online so please do check one or two, I am sure you will find it very simple to use.
The point is that you can encode concrete classes or structures directly to (and from) JSON. So your problem is then not in JSON itself but just structuring your classes. For instance:
class Survey {
class Answer {
enum AnswerType: String {
case imagepicker
}
class Value {
var value: String?
}
var type: AnswerType = .imagepicker
var qcode: String?
var values: [Value] = [Value]()
}
let interactionId: String
var futureSurveyAnswers: [Answer] = [Answer]()
var originalResultArray: String?
init(interactionId: String) { self.interactionId = interactionId }
}
This way you can easily modify your structure as you want to. For instance:
let survey = Survey(interactionId: "0")
survey.futureSurveyAnswers.append({
let answer = Survey.Answer()
answer.qcode = "test"
return answer
}())
So to extend it to codable all you need to do is actually append Codable protocol to your class and any embedded component:
class Survey: Codable {
class Answer: Codable {
enum AnswerType: String, Codable {
case imagepicker
}
class Value: Codable {
var value: String?
}
var type: AnswerType = .imagepicker
var qcode: String?
var values: [Value] = [Value]()
}
let interactionId: String
var futureSurveyAnswers: [Answer] = [Answer]()
var originalResultArray: String?
init(interactionId: String) { self.interactionId = interactionId }
}
Now you can get JSON as simple as the following:
let survey = Survey(interactionId: "0")
survey.futureSurveyAnswers.append({
let answer = Survey.Answer()
answer.values.append({
let value = Survey.Answer.Value()
value.value = "some"
return value
}())
answer.qcode = "test"
return answer
}())
let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(survey) {
print("Generated data: \(jsonData)")
}
I hope this puts you in the right direction.
From comments this is what I used as a test example for JSON:
class Survey: Codable {
class Answer: Codable {
enum AnswerType: String, Codable {
case imagepicker
}
class Value: Codable {
var value: String?
init(value: String? = nil) { self.value = value }
}
var type: AnswerType = .imagepicker
var qcode: String?
var values: [Value] = [Value]()
}
let interactionId: String
var futureSurveyAnswers: [Answer] = [Answer]()
var originalResultArray: String?
init(interactionId: String) { self.interactionId = interactionId }
}
func addTestAnswer(to survey: Survey) {
let answer = Survey.Answer()
answer.values.append(.init(value: "Random value \(Int.random(in: 1...100))"))
answer.values.append(.init(value: "Random value \(Int.random(in: 1...100))"))
answer.values.append(.init(value: "Random value \(Int.random(in: 1...100))"))
answer.values.append(.init(value: "Random value \(Int.random(in: 1...100))"))
survey.futureSurveyAnswers.append(answer)
}
func testRandomSurveyJSON() {
let survey = Survey(interactionId: "randomSurvey")
addTestAnswer(to: survey)
addTestAnswer(to: survey)
addTestAnswer(to: survey)
addTestAnswer(to: survey)
addTestAnswer(to: survey)
print("Got JSON: \(String(data: try! JSONEncoder().encode(survey), encoding: .utf8)!)")
}
And got the following result:
Got JSON: {"interactionId":"randomSurvey","futureSurveyAnswers":[{"type":"imagepicker","values":[{"value":"Random value 74"},{"value":"Random value 4"},{"value":"Random value 26"},{"value":"Random value 93"}]},{"type":"imagepicker","values":[{"value":"Random value 43"},{"value":"Random value 65"},{"value":"Random value 38"},{"value":"Random value 88"}]},{"type":"imagepicker","values":[{"value":"Random value 56"},{"value":"Random value 88"},{"value":"Random value 57"},{"value":"Random value 94"}]},{"type":"imagepicker","values":[{"value":"Random value 66"},{"value":"Random value 52"},{"value":"Random value 89"},{"value":"Random value 27"}]},{"type":"imagepicker","values":[{"value":"Random value 53"},{"value":"Random value 93"},{"value":"Random value 30"},{"value":"Random value 55"}]}]}
which seems to be correct.

Swift: Constant in Template Definition

I'm working with a backend developer that likes to encapsulate json bodies in another object such as data:
Example:
GET: /user/current:
{
data: {
firstName: "Evan",
lastName: "Stoddard"
}
}
I would simply like to just call json decode on the response to get a User struct that I've created but the added data object requires another struct. To get around this I created a generic template class:
struct DecodableData<DecodableType:Decodable>:Decodable {
var data:DecodableType
}
Now I can get my json payload and if I want to get a User struct just get the data property of my template:
let user = JSONDecoder().decode(DecodableData<User>.self, from: jsonData).data
This is all fine and dandy until sometimes, the key, data, isn't always data.
I feel like this is most likely fairly trivial stuff, but is there a way I can add a parameter in my template definition so I can change the enum coding keys as that data key might change?
Something like the following?
struct DecodableData<DecodableType:Decodable, Key:String>:Decodable {
enum CodingKeys: String, CodingKey {
case data = Key
}
var data:DecodableType
}
This way I can pass in the target decodable class along with the key that encapsulates that object.
No need for coding keys. Instead, you need a simple container that parses the JSON as a dictionary that has exactly one key-value pair, discarding the key.
struct Container<T>: Decodable where T: Decodable {
let value: T
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dict = try container.decode([String: T].self)
guard dict.count == 1 else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "expected exactly 1 key value pair, got \(dict.count)")
}
value = dict.first!.value
}
}
If the JSON is empty or has more than one key-value pair, an exception is raised.
Assuming a simple struct such as
struct Foo: Decodable, Equatable {
let a: Int
}
you can parse it regardless of the key:
let foo1 = try! JSONDecoder().decode(
Container<Foo>.self,
from: #"{ "data": { "a": 1 } }"#.data(using: .utf8)!
).value
let foo2 = try! JSONDecoder().decode(
Container<Foo>.self,
from: #"{ "doesn't matter at all": { "a": 1 } }"#.data(using: .utf8)!
).value
foo1 == foo2 // true
This also works for JSON responses that have null as the value, in which case you need to parse it as an optional of your type:
let foo = try! JSONDecoder().decode(
Container<Foo?>.self,
from: #"{ "data": null }"#.data(using: .utf8)!
).value // nil
Try with something like this:
struct GenericCodingKey: CodingKey {
var stringValue: String
init(value: String) {
self.stringValue = value
}
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
struct DecodableData<DecodableType: CustomDecodable>: Decodable {
var data: DecodableType
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: GenericCodingKey.self)
data = try container.decode(DecodableType.self, forKey: GenericCodingKey(value: DecodableType.dataKey))
}
}
protocol CustomDecodable: Decodable {
static var dataKey: String { get }
}
extension CustomDecodable {
static var dataKey: String {
return "data" // This is your default
}
}
struct CustomDataKeyStruct: CustomDecodable {
static var dataKey: String = "different"
}
struct NormalDataKeyStruct: CustomDecodable {
//Change Nothing
}

How to Store and Fetch Json Model data to Coredata in Swift

I have used coredata long back. But, I know basics of coredata for storing data and fetching.
But, Presently I am working with Swift language.
I have local json file and I am doing parsing that by decoder and displaying that data in tableview.
let path = Bundle.main.path(forResource: "file", ofType: "json")
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path ?? ""), options: .mappedIfSafe)
let decoder = JSONDecoder()
do {
quData = try decoder.decode(quData.self, from: data)
DispatchQueue.main.async {
self.myTableView.reloadData()
}
} catch {
print("Json decoder error")
}
} catch {
print(LocalizedError.self)
}
For that I have created model class based on the key values of json.
But, Now I have to store that data to Coredata and Fetch back, Need to show in same tableview.
But, I am getting confusion how many key values should I need to create.
My model class is :
class QuData: Codable {
let qu: Qu
init(qu: Qu) {
self.qu = qu
}
}
class Qu: Codable {
let music: Music
let dance: dance
init(music: Music, dance: dance) {
self.music = music
self.dance = dance
}
}
class Maths: Codable {
let q1, q2: Q1
init(q1: Q1, q2: Q1) {
self.q1 = q1
self.q2 = q2
}
}
class Q1: Codable {
let query: String
let options: [String]
let answer: String
let q1Optional: Bool
enum CodingKeys: String, CodingKey {
case query, options, answer
case q1Optional = "optional"
}
init(question: String, options: [String], answer: String, q1Optional: Bool) {
self.query = query
self.options = options
self.answer = answer
self.q1Optional = q1Optional
}
}
class Sport: Codable {
let q1: Q1
init(q1: Q1) {
self.q1 = q1
}
}
And my JSON data is
{
"qu": {
"music": {
"q1": {
“query”: “What is your name?”,
"options": [
“Sony”,
“Samsung”,
“Apple”,
“MI”
],
"answer": “Apple”,
"optional": true
}
},
“dance”: {
"q1": {
"question": "5 + 1 = ?",
"options": [
“8”,
“9”,
“6”,
“23”
],
"answer": “23”,
"optional": false
},
"q2": {
"question": "12 - 4 = ?",
"options": [
“5”,
“4”,
“9”,
“6”
],
"answer": "4",
"optional": false
}
}
}
}
How to store these data to Coredata and fetching, Showing in tableview..
And, The two categories (music,dance) in json data, I have to show "Music" data in 1st section and "Dance" data in
section tableview.
I am fully struck, how to create this kind json structure in Entity with attributes and fetching them using same model class (Which already created for local json file parsing).
Can anyone suggest me to move on further?
My suggestion is to use one entity.
In Core Data you can filter records very efficiently, so add a type attribute representing music, dance etc. You can even add a computed property to map the type attribute to an enum. The options attribute is declared as flat string. Use another computed property to map the flat string to an array and vice versa.
class Question : NSManagedObject {
#NSManaged var type: String
#NSManaged var question: String
#NSManaged var answer: String
#NSManaged var options: String
#NSManaged var optional: Bool
enum QuestionType : String {
case music, dance
}
var questionType : QuestionType {
get { return QuestionType(rawValue: type)! }
set { type = newValue.rawValue }
}
var questionOptions : [String] {
get { return options.components(separatedBy: ", ") }
set { options = newValue.joined(separator: ", ") }
}
Alternatively use one entity per type and relationships for the questions
class Music : NSManagedObject {
#NSManaged var questions: Set<Question>
...
}
class Question : NSManagedObject {
#NSManaged var question: String
#NSManaged var answer: String
#NSManaged var options: String
#NSManaged var optional: Bool
#NSManaged var type: Music
...
}

Parse the array without a key before the square brackets in SwiftyJson?

Am fetching the country list from server. And the server response is like this.
[
{
"CountryID": 2,
"Name": "Afghanistan",
"Code": "AFG",
"CreatedDate": "2018-01-09T02:05:02.08"
},
{
"CountryID": 3,
"Name": "Aland Islands",
"Code": "ALA",
"CreatedDate": "2018-01-09T02:05:02.08"
}
]
Am using SwiftyJSON to convert the response as Json like this.
if let value = response.result.value {
let json = JSON(value)
let countryListData = CountryList(fromJson: json)
completionHandler(true, countryListData)
}
And the Country List class be like this.
class CountryList {
var countries: [Country]!
init(fromJson json: JSON!) {
let countryArray = json[0].arrayValue
for countryJson in countryArray {
let value = Country(fromJson: countryJson)
countries.append(value)
}
}
}
class Country {
var code : String!
var countryID : Int!
var createdDate : String!
var name : String!
init(fromJson json: JSON!){
if json == nil{
return
}
code = json["Code"].stringValue
countryID = json["CountryID"].intValue
createdDate = json["CreatedDate"].stringValue
name = json["Name"].stringValue
}
}
How can I parse this array without a key before the square brackets in SwiftyJson? It doesn't give the array objects correctly.
I know this to do it in normal way like will convert the response as dictionary. But client advised to use SwiftyJson. So only am trying this in SwiftyJson.
Give me some suggestion and don't mark this question as duplicate. Because I don't get any reference from the internet to convert this by using SwiftyJson.
Two issues in your class CountryList
class CountryList {
// 1. the countries var is not initialized
// var countries: [Country]!
// Initialize it like below
var countries = [Country]()
init(fromJson json: JSON!) {
// 2 issue is that json itself is an array so no need of doing json[0].arrayValue
let countryArray = json.arrayValue
for countryJson in countryArray {
let value = Country(fromJson: countryJson)
countries.append(value)
}
}
}
I Found the answer by myself. I converted the response as NSArray like this
do {
let array = try JSONSerialization.jsonObject(with: response.data!) as? NSArray
let countryListData = CountryList(fromArray: array!)
completionHandler(true, countryListData)
} catch {
print("Exception occured \(error))")
}
And changed the CountryList class like this
class CountryList {
var countries = [Country]()
init(fromArray array: NSArray!) {
for countryJson in array {
let value = Country(fromJson: countryJson)
countries.append(value)
}
}
}
class Country {
var code : String!
var countryID : Int!
var createdDate : String!
var name : String!
init(fromJson json: JSON!){
if json == nil{
return
}
code = json["Code"].stringValue
countryID = json["CountryID"].intValue
createdDate = json["CreatedDate"].stringValue
name = json["Name"].stringValue
}
}
Now its working what I expected. Thanks for all your comments and answer.

Codable object mapping array element to string

I have a (annoying) situation where my back-end returns an object like this:
{
"user": {
"name": [
"John"
],
"familyName": [
"Johnson"
]
}
}
where each property is an array that holds a string as its first element. In my data model struct I could declare each property as an array but that really would be ugly. I would like to have my model as such:
struct User: Codable {
var user: String
var familyName: String
}
But this of course would fail the encoding/decoding as the types don't match. Until now I've used ObjectMapper library which provided a Map object and currentValue property, with that I could declare my properties as String type and in my model init method assig each value through this function:
extension Map {
public func firstFromArray<T>(key: String) -> T? {
if let array = self[key].currentValue as? [T] {
return array.first
}
return self[key].currentValue as? T
}
}
But now that I am converting to Codable approach, I don't know how to do such mapping. Any ideas?
You can override init(from decoder: Decoder):
let json = """
{
"user": {
"name": [
"John"
],
"familyName": [
"Johnson"
]
}
}
"""
struct User: Codable {
var name: String
var familyName: String
init(from decoder: Decoder) throws {
let container:KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self)
let nameArray = try container.decode([String].self, forKey: .name)
let familyNameArray = try container.decode([String].self, forKey: .familyName)
self.name = nameArray.first!
self.familyName = familyNameArray.first!
}
enum CodingKeys: String, CodingKey {
case name
case familyName
}
}
let data = json.data(using: .utf8)!
let decodedDictionary = try JSONDecoder().decode(Dictionary<String, User>.self, from: data)
print(decodedDictionary) // ["user": __lldb_expr_48.User(name: "John", familyName: "Johnson")]
let encodedData = try JSONEncoder().encode(decodedDictionary["user"]!)
let encodedStr = String(data: encodedData, encoding: .utf8)
print(encodedStr!) // {"name":"John","familyName":"Johnson"}
My tendency would be to adapt your model to the data coming in and create computed properties for use in the application, e.g.
struct User: Codable {
var user: [String]
var familyName: [String]
var userFirstName: String? {
return user.first
}
var userFamilyName: String? {
return familyName.first
}
}
This allows you to easily maintain parody with the data structure coming in without the maintenance cost of overriding the coding/decoding.
If it goes well with your design, you could also have a UI wrapper Type or ViewModel to more clearly differentiate the underlying Model from it's display.

Resources