I have a JSON response (bellow) and I need to parse this -
[
{
"id":123,
"name":"Fahim Rahman",
"age":25,
"friends":[
{
"firstName": "Imtiaz",
"lastName": "Khan",
"avatar_url": null
}
],
"groups":{
"xcet":{
"name":"xcek cert etsh tnhg",
"createdDate":"2022-10-31T10:00:48Z"
},
"juyt":{
"name":"jfd uyt you to",
"createdDate":"2021-09-13T10:00:00Z"
},
"some random key":{
"name": "some name",
"createdDate":"2026-03-27T10:00:00Z"
}
}
}
]
To parse this in my code I've created this model. I can not able to parse the groups as that is not a list but an object -
import ObjectMapper
class Person: BaseObject {
#objc dynamic var ID: Int = -1
#objc dynamic var name: String = ""
#objc dynamic var age: Int = -1
var friendsList = List<Friends>()
override func mapping(map: ObjectMapper.Map) {
ID <- map["id"]
name <- map["name"]
age <- map["age"]
friendsList <- map["friends"]
}
}
class Friends: BaseObject {
#objc dynamic var firstName: String = ""
#objc dynamic var lastName: String = ""
#objc dynamic var avatarURL: String = ""
override func mapping(map: ObjectMapper.Map) {
firstName <- map["firstName"]
lastName <- map["name"]
avatarURL <- map["avatar_url"]
}
}
I know it's a bad JSON. The groups should be on the list instead of the nested objects but unfortunately, I'm getting this response.
Here in the response of groups, the number of nested objects is dynamic and the key of the nested object is also dynamic. Thus I can not able to parse this as friends attribute.
So my question is, how can I map the "groups"?
Before mapping groups, we need a class that can hold each Group alongside its key (i.e. xct)
For example
Class Groups: BaseObject {
#objc dynamic var key: String = ""
#objc dynamic var value: GroupsItem?
convenience init(key: String, value: GroupsItem) {
self.init()
self.key = key
self.value = value
}
}
Class GroupsItem: BaseObject {
#objc dynamic var name: String?
#objc dynamic var createdDate: String?
...
}
Then inside your Person class you can map this as -
private func mapGroupsItems(map: ObjectMapper.Map) -> List<GroupsItem> {
var rowsDictionary: [String: Groups]?
rowsDictionary <- map["groups"]
let rows = List<GroupsItem>()
if let dictionary = rowsDictionary {
for (key, value) in dictionary {
rows.append(GroupsItem(key: key, value: value))
}
}
return rows
}
dont forget to call this method from mapping -
override public func mapping(map: ObjectMapper.Map) {
...
groups = mapGroupsItems(map: map)
}
try this approach, using a custom init(from decoder: Decoder) for Groups, works well for me. Use a similar approach for non-SwiftUI systems.
struct ContentView: View {
#State var people: [Person] = []
var body: some View {
ForEach(people) { person in
Text(person.name)
ForEach(Array(person.groups.data.keys), id: \.self) { key in
Text(key).foregroundColor(.red)
Text(person.groups.data[key]?.name ?? "no name").foregroundColor(.blue)
Text(person.groups.data[key]?.createdDate ?? "no date").foregroundColor(.blue)
}
}
.onAppear {
let json = """
[
{
"id":123,
"name":"Fahim Rahman",
"age":25,
"friends":[
{
"firstName": "Imtiaz",
"lastName": "Khan",
"avatar_url": null
}
],
"groups":{
"xcet":{
"name":"xcek cert etsh tnhg",
"createdDate":"2022-10-31T10:00:48Z"
},
"juyt":{
"name":"jfd uyt you to",
"createdDate":"2021-09-13T10:00:00Z"
},
"some random key":{
"name": "some name",
"createdDate":"2026-03-27T10:00:00Z"
}
}
}
]
"""
if let data = json.data(using: .utf8) {
do {
self.people = try JSONDecoder().decode([Person].self, from: data)
print("---> people: \(people)")
} catch {
print("decode error: \(error)")
}
}
}
}
}
struct Person: Identifiable, Codable {
let id: Int
var name: String
var age: Int
var friends: [Friend]
var groups: Groups
}
struct Friend: Codable {
var firstName, lastName: String
var avatarURL: String?
enum CodingKeys: String, CodingKey {
case firstName, lastName
case avatarURL = "avatar_url"
}
}
struct Info: Codable {
var name: String
var createdDate: String
}
struct Groups: Identifiable, Codable {
let id = UUID()
var data: [String:Info] = [:]
init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
self.data = try container.decode([String:Info].self)
} catch {
print(error)
}
}
}
Your Model classes structure will be
// MARK: - Welcome7Element
struct Welcome7Element {
let id: Int
let name: String
let age: Int
let friends: [Friend]
let groups: Groups
}
// MARK: - Friend
struct Friend {
let firstName, lastName: String
let avatarURL: NSNull
}
// MARK: - Groups
struct Groups {
let xcet, juyt, someRandomKey: Juyt
}
// MARK: - Juyt
struct Juyt {
let name: String
let createdDate: Date
}
Thank you #shakif_ for your insightful answer. Here is how I solved this based on that answer -
import ObjectMapper
import RealmSwift
class Person: BaseObject {
#objc dynamic var ID: Int = -1
#objc dynamic var name: String = ""
#objc dynamic var age: Int = -1
var friendsList = List<Friends>()
var groups = List<Group>
override func mapping(map: ObjectMapper.Map) {
ID <- map["id"]
name <- map["name"]
age <- map["age"]
friendsList <- map["friends"]
groups = extractGroups(map)
}
private func extractGroups(_ map: ObjectMapper.Map) -> List<Group> {
let items = List<Group>()
var modifiedJSON = [String: Group]
modifiedJSON <- map["groups"]
for (key,value) in modifiedJSON {
let item = GroupMapper(key: key, value: value)
if let group = item.value {
items.append(group)
}
}
return items
}
}
class Friends: BaseObject {
#objc dynamic var firstName: String = ""
#objc dynamic var lastName: String = ""
#objc dynamic var avatarURL: String = ""
override func mapping(map: ObjectMapper.Map) {
firstName <- map["firstName"]
lastName <- map["name"]
avatarURL <- map["avatar_url"]
}
}
class Group: BaseObject {
#objc dynamic var name: String = ""
#objc dynamic var createdDate: String = ""
override func mapping(map: ObjectMapper.Map) {
name <- map["name"]
createdDate <- map["createdDate"]
}
}
struct GroupMapper {
var key: String = ""
var value: Group?
}
Related
This might be something really easy but I don't understand how to do it:
so I have this DTO struct I use to get API data into it and map it to Model struct
my DTO:
struct PetDTO: Codable {
var id: Int
var category: CategoryDTO?
var name: String?
var photoUrls: [String]?
var tags: [TagDTO]?
var status: StatusDTO?
}
public class CategoryDTO: NSObject, Codable {
var id: Int
var name: String?
private enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
}
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
}
}
public class TagDTO: NSObject, Codable {
var id: Int
var name: String?
private enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
}
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
}
}
enum StatusDTO: String, Codable {
case available
case sold
case pending
}
And my model:
struct PetDataModel {
var id: Int
var category: Category
var name: String?
var photoUrls: [String]?
var tags: [Tags]?
var status: Status?
init(petDto: PetDTO) {
self.id = petDto.id
self.category = Category(categoryDto: petDto.category)
self.name = petDto.name
self.photoUrls = petDto.photoUrls
for tag in petDto.tags ?? [] {
self.tags = [Tags(tagDTO: tag)] // petDto?.map { Tags(tagDTO: $0) }
}
self.status = Status(rawValue: petDto.status?.rawValue)
}
}
struct Category {
var id: Int
var name: String?
init(categoryDto: CategoryDTO) {
self.id = categoryDto.id
self.name = categoryDto.name
}
}
struct Tags {
var id: Int
var name: String?
init(tagDTO: TagDTO) {
self.id = tagDTO.id
self.name = tagDTO.name
}
}
enum Status: String, Codable {
case available
case sold
case pending
}
As you can see, the mapping happens in Init of PetDataModel. I have errors on this lines
Please tell me how to fix this without making CategoryDto from PetDTO non optional, I need it to stay optional.
You can make category form your PetDataModel optional too.
struct PetDataModel {
var id: Int
var category: Category?
var name: String?
var photoUrls: [String]?
var tags: [Tags]?
var status: Status?
init(petDto: PetDTO) {
self.id = petDto.id
self.category = Category(categoryDto: petDto.category)
self.name = petDto.name
self.photoUrls = petDto.photoUrls
for tag in petDto.tags ?? [] {
self.tags = [Tags(tagDTO: tag)] // petDto?.map { Tags(tagDTO: $0) }
}
self.status = Status(rawValue: petDto.status?.rawValue)
}
}
and make your initializer optional:
struct Category {
var id: Int
var name: String?
init?(categoryDto: CategoryDTO?) {
guard let categoryDto = categoryDto else{
return nil
}
self.id = categoryDto.id
self.name = categoryDto.name
}
}
if you donĀ“t want an optional initializer you can check and assign like this:
self.category = petDto.category != nil ? Category(categoryDto: petDto.category!) : nil
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.
I'm making an app for airports and I'm getting an array of data from one api, like so:
"data":[
{"id":"001","code":"ABZ","name":"Aberdeen","country":"United Kingdom"},
{"id":"002","code":"AUH","name":"Abu Dhabi","country":"United Arab Emirates"},
.
.
.
]
AND :
"airports":[
{"from":"001",
"to":["1","3","11","13","12","20","23","27","29","31","33"]
},
.
.
.
]
I have created realm model classes:
class AirportsDataRealm: Object {
#objc dynamic var name: String = ""
#objc dynamic var id: Int = 0
#objc dynamic var code: String = ""
#objc dynamic var country: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
class AirportsFromToRealm: Object {
#objc dynamic var fromID: Int = 0
var toID = List<Int>()
override static func primaryKey() -> String? {
return "fromID"
}
}
now I want to save it into realm, I'm using swiftyJSON and I have used for-loop to do it and it is working fine but I think it's taking long time since the array is very long, here is what I've done:
// Airports Data
let countData = json["data"].count
for i in 0...countData - 1{
let airportsDataModel = AirportsDataRealm()
airportsDataModel.code = json["data"][i]["code"].stringValue
airportsDataModel.name = json["data"][i]["name"].stringValue
airportsDataModel.country = json["data"][i]["country"].stringValue
airportsDataModel.id = Int(json["data"][i]["id"].stringValue)!
try! realm.write {
realm.add(airportsDataModel, update: true)
}
}
//Airports FROM-TO
let countFromTo = json["airports"].count
for i in 0...countFromTo - 1{
let fromToDataModel = AirportsFromToRealm()
fromToDataModel.fromID = Int(json["airports"][i]["from"].stringValue)!
let arrayTo = json["airports"][i]["to"].arrayValue.map{ $0.intValue }
fromToDataModel.toID.append(objectsIn: arrayTo)
try! realm.write {
realm.add(fromToDataModel, update: true)
}
}
is there any way to save the whole array in realm in one shot without for-loop?
P.S
"there should be a relation between the two tables because each from 'id' has a list of 'to' id's and the id's are from the data table, for now I managed to create this relations when fetching the data using filters ,, so just ignore this"
Thank you
Simply use map method,
First I needed to add initializers to my object classes and pass json array as a parameter, like so:
class AirportsDataRealm: Object {
#objc dynamic var name: String = ""
#objc dynamic var id: Int = 0
#objc dynamic var code: String = ""
#objc dynamic var country: String = ""
convenience required init(withJSON json : JSON) {
self.init()
self.name = json["name"].stringValue
self.id = json["id"].intValue
self.code = json["code"].stringValue
self.country = json["country"].stringValue
}
override static func primaryKey() -> String? {
return "id"
}
}
class AirportsFromToRealm: Object {
#objc dynamic var fromID: Int = 0
var toID = List<Int>()
convenience required init(withJSON json : JSON) {
self.init()
self.fromID = json["from"].intValue
let toArray = json["to"].arrayValue.map{ $0.intValue }
self.toID.append(objectsIn: toArray)
}
override static func primaryKey() -> String? {
return "fromID"
}
}
Then by using map method the code will look like this:
func updateAirport(json: JSON) {
// Airports Data
let airportsData : [AirportsDataRealm]
let airportsDataJsonArray = json["data"].array
airportsData = airportsDataJsonArray!.map{AirportsDataRealm(withJSON: $0)}
//Airports FROM-TO
let airportsFromTo : [AirportsFromToRealm]
let airportsFromToJsonArray = json["airports"].array
airportsFromTo = airportsFromToJsonArray!.map{AirportsFromToRealm(withJSON: $0)}
//Write To Realm
try! realm.write {
realm.add(airportsData, update: true)
realm.add(airportsFromTo, update: true)
}
}
No for loops anymore ^_^
I started to use ObjectMapper this week and I'm trying to map a JSON into 2 CustomClasses but I don't known if ObjectMapper has some function to do what I want. First CustomClass has a property of type: [String:CustomClass2] where the index of this Dictionary should be property ID of second CustomObject.
JSON Used:
{
"types": [
{
"id": "mk8QPMSo2xvtSoP0cBUD",
"name": "type 1",
"img": "type_1",
"showCategories": false,
"modalityHint": [
"K7VqeFkRQNXoh2OBxgIf"
],
"categories": [
"mP3MqbJrO5Da1dVAPRvk",
"SlNezp2m3PECnTyqQMUV"
]
}
]
}
Classes Used:
class MyClass: Mappable {
var types:[String:MyClass2] = [String:MyClass2]() //Index should be ID property of MyClass2 Object
required init?(map:Map) {
guard map.JSON["types"] != nil else {
return nil
}
}
func mapping(map: Map) {
types <- map["types"]
}
}
class MyClass2: Mappable {
private var id: String!
private var name: String!
private var img: String!
private var showCategories: Bool!
private var modalityHint: [String]?
private var categories: [String]?
required init?(map: Map) { }
func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
img <- map["img"]
showCategories <- map["showCategories"]
modalityHint <- map["modalityHint"]
categories <- map["categories"]
}
In your JSON the types key is an array not a Dictionary.
Change:
var types:[String:MyClass2] = [String:MyClass2]()
To:
var types:[Class2] = []
Like this:
class MyClass: Mappable {
private var arrayTypes = [MyClass2] {
didSet{
var mapTypes = [String:MyClass2]?
for obj in arrayTypes {
mapTypes[obj.id] = obj
}
types = mapTypes
}
}
var types:[String:MyClass2] = [String:MyClass2]()
required init?(map:Map) {
guard map.JSON["types"] != nil else {
return nil
}
}
func mapping(map: Map) {
arrayTypes <- map["types"]
}
}
Iam new in Swift and i want to convert my struct into parameter to post it with Alamofire 4.
Please excuse my bad english.
My struct is in another Class:
import Foundation
class structUser: NSObject{
var myStructUser = [person]()
struct person {
var firstName : String
var lastName : String
var age: Int
init ( firstName : String, lastName : String, age : Int) {
self.firstName = firstName
self.lastName = lastName
self.age = age
}
}
override init(){
myStructUser.append(person(firstName: "John", lastName: "Doe", age: 11))
myStructUser.append(person(firstName: "Richard", lastName: "Brauer", age : 22))
myStructUser.append(person(firstName: "Merrideth", lastName: "Lind", age : 55))
}
}
now in the Main Class I want to post the Alamofire, but how can I convert only the first name and the age from the struct?
import UIKit
class ViewController: UIViewController {
let classStructUser = structUser()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
print(classStructUser.myStructUser)
}
func postJson(){
//need format [String : Any]
for item in classStructUser.myStructUser{
// var name = classStructUser.myStructUser.name
// var age = classStructUser.myStructUser.age
}
print(classStructUser.myStructUser)
/*here i need the Json in format:
{
"name":"John",
"age":11
}
{
"name":"Richard",
"age":22
}
{
"name":"Merrideth",
"age":55
}
an so on array.count
*/
}
}
another Question:
How can I access a variable in the struct(structUser) from VieControllerClass
thx for your help! And please explain the full solution, because i want to understand how ist works.
Thx!
class structUser: NSObject{
var myStructUser = [person]()
struct person {
var firstName : String
var lastName : String
var age: Int
init ( firstName : String, lastName : String, age : Int) {
self.firstName = firstName
self.lastName = lastName
self.age = age
}
static func jsonArray(array : [person]) -> String
{
return "[" + array.map {$0.jsonRepresentation}.joined(separator: ",") + "]"
}
var jsonRepresentation : String {
return "{\"name\":\"\(firstName)\",\"age\":\"\(age)\"}"
}
}
func jsonRepresentation() -> String {
return person.jsonArray(array: myStructUser)
}
override init(){
myStructUser.append(person(firstName: "John", lastName: "Doe", age: 11))
myStructUser.append(person(firstName: "Richard", lastName: "Brauer", age : 22))
myStructUser.append(person(firstName: "Merrideth", lastName: "Lind", age : 55))
}
}
And use it like this.
let jsonString = classStructUser.myStructUser.jsonRepresentation()