0

I wanted to parse local JSON and access inside content using JSON decoder. I'm new to JSON decoder can anyone pls suggest.

JSON:

[
  {
    "bookmark_intro": {
      "title": "What's new in bookmarks",
      "enabled": "yes",
      "content": [
        {
          "subtitle": "Organize with folders",
          "content": "Organize your bookmarks in folders for quick and easy access.",
          "icon": "image1.png"
        },
        {
          "subtitle": "Share and subscribe",
          "content": "Share your folders with your colleagues and subscribe to their folders to keep you informed about updates.",
          "icon": "image2.png"
        },
        {
          "subtitle": "And lots more!",
          "content": "Edit bookmarks easier, add bookmarks to multiple folders - all that even offline and synced across all your apps and devices.",
          "icon": "image3.png"
        }
      ]
    }
  }
]

Created Model as below:

struct PremiumTutorialModel : Codable {
    let bookmark_intro : Bookmark_intro?
    enum CodingKeys: String, CodingKey {
        case bookmark_intro = "bookmark_intro"
    }
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        bookmark_intro = try values.decodeIfPresent(Bookmark_intro.self, forKey: .bookmark_intro)
    }
}

struct Bookmark_intro : Codable {
    let title : String?
    let enabled : String?
    let content : [Content]?
    enum CodingKeys: String, CodingKey {
        case title = "title"
        case enabled = "enabled"
        case content = "content"
    }
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        title = try values.decodeIfPresent(String.self, forKey: .title)
        enabled = try values.decodeIfPresent(String.self, forKey: .enabled)
        content = try values.decodeIfPresent([Content].self, forKey: .content)
    }
}

struct Content : Codable {
    let subtitle : String?
    let content : String?
    let icon : String?
    enum CodingKeys: String, CodingKey {
        case subtitle = "subtitle"
        case content = "content"
        case icon = "icon"
    }
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        subtitle = try values.decodeIfPresent(String.self, forKey: .subtitle)
        content = try values.decodeIfPresent(String.self, forKey: .content)
        icon = try values.decodeIfPresent(String.self, forKey: .icon)
    }
}

I was trying to parse and access data using this function it wasn't returning full data on the model, can anyone suggest the correct way of doing this.

   func loadJson(fileName: String) -> PremiumTutorialModel? {
        let decoder = JSONDecoder()
        guard
            let url = Bundle.main.url(forResource: fileName, withExtension: "json"),
            let data = try? Data(contentsOf: url),
            let model = try? decoder.decode(PremiumTutorialModel.self, from: data)
        else {
            return nil
        }
        return model
    }

Can anyone suggest correct way of doing parsing json with JSON Decoder.

1
  • 1
    For starters, the outmost type in your json is an array so it should be decoder.decode([PremiumTutorialModel].self..., you shouldn't need any custom init(from:) for your own local json, change the json file instead and only make your properties optional if needed. Furthermore, while developing and testing you should use proper error handling when decoding. The error message are often very helpful. Commented Dec 22, 2021 at 16:03

2 Answers 2

1

You can create these models:

import Foundation

// MARK: - PremiumTutorialModelElement
struct PremiumTutorialModelElement: Codable {
    let bookmarkIntro: BookmarkIntro

    enum CodingKeys: String, CodingKey {
        case bookmarkIntro = "bookmark_intro"
    }
}

// MARK: - BookmarkIntro
struct BookmarkIntro: Codable {
    let title: String
    let enabled: String
    let content: [Content]

    enum CodingKeys: String, CodingKey {
        case title = "title"
        case enabled = "enabled"
        case content = "content"
    }
}

// MARK: - Content
struct Content: Codable {
    let subtitle: String
    let content: String
    let icon: String

    enum CodingKeys: String, CodingKey {
        case subtitle = "subtitle"
        case content = "content"
        case icon = "icon"
    }
}

typealias PremiumTutorialModel = [PremiumTutorialModelElement]

This website is great for generating JSON -> Swift models.

EDIT:

As @Sulthan pointed out, in this case the CodingKeys are redundant. I included them for clarity and to make it easier for others to modify, but this will work as well:

import Foundation

// MARK: - PremiumTutorialModelElement
struct PremiumTutorialModelElement: Codable {
    let bookmarkIntro: BookmarkIntro

    enum CodingKeys: String, CodingKey {
        case bookmarkIntro = "bookmark_intro"
    }
}

// MARK: - BookmarkIntro
struct BookmarkIntro: Codable {
    let title, enabled: String
    let content: [Content]
}

// MARK: - Content
struct Content: Codable {
    let subtitle, content, icon: String
}

typealias PremiumTutorialModel = [PremiumTutorialModelElement]
Sign up to request clarification or add additional context in comments.

7 Comments

CodingKeys are not necessary in this case.
@Sulthan True. In this case, they are redundant for the structs. I prefer adding them for clarity and it makes it easier for others to adjust the code to their needs.
At least, you can remove the assignments and leave them only as case subtitle instead of case subtitle = "subtitle"
@Sulthan Again, true. And again I think it's easier for others to modify to their needs when seeing this. What if the name in JSON is "subtitle", but you want to call the property description? Then it's nice to have this as a template.
Actually, no. Adding unnecessary code means that 1/ it's harder to read the code 2/ increased chance of bugs. 3/ necessary time to write that code that is not necessary in the first place. Add code when you need it.
|
0

I created a method which can access json as follow it's working.

    func loadJson(fileName: String) -> PremiumTutorialModel? {
    let decoder = JSONDecoder()
    guard
        let url = Bundle.main.url(forResource: fileName, withExtension: "json"),
        let data = try? Data(contentsOf: url),
        let model = try? decoder.decode([PremiumTutorialModel].self, from: data)
    else {
        return nil
    }
    return model[0]
}

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.