0

I have a JSON and i need it to convert it into array of objects. This is my JSON ( short version of it )

[
{
    "categoryID": 5,
    "categoryDescription": "Trips",
    "groupID": 43,
    "groupDescription": "USA",
    "groupImage": "e613c87a-4dab-4929-90cf-2b584fdf0399.jpg",
    "subgroupDescription": "2 days",
    "subgroupPrice": "200"
},
{
   "categoryID": 5,
    "categoryDescription": "Trips",
    "groupID": 43,
    "groupDescription": "USA",
    "groupImage": "e613c87a-4dab-4929-90cf-2b584fdf0399.jpg",
    "subgroupDescription": "5 days",
    "subgroupPrice": "500"
},
{
    "categoryID": 5,
    "categoryDescription": "Trips",
    "groupID": 33,
    "groupDescription": "Mexico",
    "groupImage": "e613c87a-4dab-4929-90cf-2b584fdf0399.jpg",
    "subgroupDescription": "3 days",
    "subgroupPrice": "400"
},
{
    "categoryID": 5,
    "categoryDescription": "Trips",
    "groupID": 33,
    "groupDescription": "Mexico",
    "groupImage": "e613c87a-4dab-4929-90cf-2b584fdf0399.jpg",
    "subgroupDescription": "1 days",
    "subgroupPrice": "150"
},
{
    "categoryID": 3,
    "categoryDescription": "Hotels",
    "groupID": 22,
    "groupDescription": "My Hotel",
    "groupImage": "e613c87a-4dab-4929-90cf-2b584fdf0399.jpg",
    "subgroupDescription": "1 night",
    "subgroupPrice": "50"
},
{
    "categoryID": 3,
    "categoryDescription": "Hotels",
    "groupID": 10,
    "groupDescription": "Your Hotel",
    "groupImage": "e613c87a-4dab-4929-90cf-2b584fdf0399.jpg",
    "subgroupDescription": "2 nights",
    "subgroupPrice": "150"
}]

and i need to make a custom objects like this : (How can I create such this object in Swift 4?)

enter image description here

In JSON we have categoryID and groupID i need to filter arrays based on them, for example i need to have only one object with categoryID 5. and i need to have only one object with groupID : 43. but i need all subgroups. This is the structure of my objects: ( I don't know if i'm on the right path )

struct Categories {
var categoryID: NSNumber
var categoryDescription: String
var groups : [Groups]}

struct Groups {
var geoupID: NSNumber
var geoupDescription: String
var groupImage: String
var subGroups : [Subgroups] }

struct Subgroups {
var subgroupPrice: NSNumber
var subgroupDescription: String }

How can I filter it in best way ?

0

2 Answers 2

2

As for how to make the structures, simply use Decodable and either name the fields according to what you expect in the JSON, or use CodingKeys to specify the names. For example:

struct MyStruct: Decodable {
    let categoryID: Int
    let categoryDescription: String
    let groupID: Int
    // …
}

Then you can decode your results as [MyStruct] using JSONDecoder.

Now, the real problem seems to be that you want to have a different structure internally than what you receive as JSON, including constraints like "only one of each categoryID". Probably the most straightforward way is to then iterate over the decoded results and copy the contents into different structures.

Instead of arrays (e.g., var groups: [Groups]) you could have a dictionary with groupID as keys to enforce there being only one of each id, for example:

guard let results = try? jsonDecoder.decode([MyStruct].self, from: json) else { return }
var categories = [Int: Category]()
for result in results {
    // fetch existing category or make a new one
    var category = categories[result.categoryID, default: Category(id: result.categoryID, description: result.categoryDescription)]

    // fetch existing group in category or make a new one
    var group = category.groups[result.groupID, default: Group(id: result.groupID, description: result.groupDescription, image: result.groupImage)]

    // append subgroup (always new since there is no id)
    let subgroup = Subgroup(description: result.subgroupDescription, price: result.subgroupPrice)
    group.subgroups.append(subgroup)

    // "save"
    category.groups[result.groupID] = group
    categories[result.categoryID] = category
}
Sign up to request clarification or add additional context in comments.

5 Comments

Note that I'm only using Decodable as it suffices for what seems to be a throwaway intermediate format, but you can just as well use Codable (to add Encodable).
Thanks for your response, I have tried this before but i have problem with "Subgroups" and "Groups", Do I have to initial Groups in the "for loop" or not ? would you please be more clear about Groups and Subgroups ???
@Sattar Make groups an empty dictionary when you create Category and subgroups an empty array when you create Group, then modify the others. See edited answer (untested code, but the idea should be clear).
BTW, if you can also affect the JSON side of things, it is quite suboptimal now since it returns duplicate category and group for every subgroup - if you had a similar nested structure in the JSON, the results would be considerably smaller and you could decode them directly into the final structures.
Thanks, your updated answer is working like a charm, I know this should be done from server side programers and few days ago I have asked them to send me the data in the correct format but they didn't so i decided to do it in the application side, anyway thanks for your help.
1

Country Json

{
      ID = 2;
      Image = "";
      Name = "";
      City =     (
        {
          ID = 74;
          Name = "";
          ParentID = 2;
      },
        {
          ID = 79;
          Image = "";
          Name = Other;
          ParentID = 2;
      }
      );
    },
    {
      ID = 31;
      Image = "";
      Name = "";
      City =     (
        {
          ID = 99;
          Name = "";
          ParentID = 31;
      },
        {
          ID = 100;
          Name = "";
          ParentID = 31;
      });
    }

My Class

class Country: Codable {

    var id: String?
    var image: String?
    var name: String?
    var cityList: [City]?

    enum CodingKeys: String, CodingKey {
      case id = "ID"
      case image = "Image"
      case name = "Name"
      case city = "City"
    }

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

      id = try? values.decode(String.self, forKey: .id)
      image = try? values.decode(String.self, forKey: .image)
      name = try? values.decode(String.self, forKey: .name)
      cityList = try? values.decode([City].self, forKey: .city)
    }

    func encode(to encoder: Encoder) throws {
      var container = encoder.container(keyedBy: CodingKeys.self)
      try container.encode(id, forKey: .id)
      try container.encode(image, forKey: .image)
      try container.encode(name, forKey: .name)
      try container.encode(cityList, forKey: .city)
    }

  }

  class City: Codable {

    var id: String?
    var name: String?
    var parentId: String?

    enum CodingKeys: String, CodingKey {
      case id = "ID"
      case name = "Name"
      case parentId = "ParentID"
    }

    required init(from decoder: Decoder) throws {
      let values = try decoder.container(keyedBy: CodingKeys.self)
      id = try? values.decode(String.self, forKey: .id)
      name = try? values.decode(String.self, forKey: .name)
      parentId = try? values.decode(String.self, forKey: .parentId)
    }

    func encode(to encoder: Encoder) throws {
      var container = encoder.container(keyedBy: CodingKeys.self)
      try container.encode(id, forKey: .id)
      try container.encode(name, forKey: .name)
      try container.encode(parentId, forKey: .parentId)
    }

  }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.