0

Example of Json I need to decode. In "text" key we have [String: String] dict. And quantity of elements is in "count". How should I decode it properly?

{
"name": "LoremIpsum",
"index": "1",
"text": {
    "text_1": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ",
    "text_2": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
    "text_3": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ",
    "text_4": "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
},
"count": "4"
}

My Codable model:

class Text: Codable {

private enum CodingKeys: String, CodingKey {
    case name, index, count, text
}

public var name: String?
public var index: Int?
public var count: Int?
public var texts: [String]?

init() {
    name = ""
    index = 0
    count = 0
    texts = []
}

init(name: String,
     index: Int,
     count: Int,
     texts: [String]) {
    self.name = name
    self.index = index
    self.count = count
    self.texts = texts
}

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    var text = container.nestedContainer(keyedBy: CodingKeys.self, forKey: . text)

}    <---- also why do I need this method? 

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

    self.name = try container.decode(String.self, forKey: .name)
    self.index = Int(try container.decode(String.self, forKey: .index)) ?? 0
    self.count = Int(try container.decode(String.self, forKey: .count)) ?? 0
    let text = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .text)

    for i in (1...self.count!) {
        self.texts!.append(try text.decode(String.self, forKey: Text.CodingKeys.init(rawValue: "text_\(i)") ?? .text))
    }
}

}

And I decode it with:

if let path = Bundle.main.path(forResource: "en_translation_001", ofType: "json") {
        do {
            let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
            let jsonObj = try JSONDecoder().decode(Text.self, from: data)
            print("jsonData:\(jsonObj)")
        } catch let error {
            print("parse error: \(error.localizedDescription)")
        }
    } else {
        print("Invalid filename/path.")
    }

But I got parse error

parse error: The data couldn’t be read because it is missing.

What is wrong with my code? Is it a good way to decode such dynamic coding keys?

3 Answers 3

2

You need

struct Root: Codable {
    let name, index,count: String
    let text: [String:String]
}

--

let res = try? JSONDecoder().decode(Root.self,from:jsonData)
Sign up to request clarification or add additional context in comments.

2 Comments

The thing is that there can be text5, text6 and textN Depending on "count"
@Said-AbdullaAtkaev use only that struct and the decode should work
2

Fix JSON

I suppose you want your index and count to be numbers. So replace this "index": "1" and this "count": "4" With this "index": 1 and this "count": 4

Class structure

With Codable protocol any of these CodingKeys and encode functions or required inits aren't neccessary. Also change texts property data format to [String: String]

So, replace your class like this:

class Text: Codable {

    public var name: String
    public var index: Int
    public var count: Int
    public var texts: [String: String]

    init(name: String, index: Int, count: Int, texts: [String: String]) {
        self.name = name
        self.index = index
        self.count = count
        self.texts = texts
    }

}

Decoding

For decoding your json use what you wrote above, this is correct

let object = try JSONDecoder().decode(Text.self, from: data)

6 Comments

@Said-AbdullaAtkaev Read edited answer. You were decoding your json right, but your class was wrong.
index and count are not ints plus texts is a dictionary not array
I need to remove all inits and also "init(from decoder: Decoder)" ?
@Said-AbdullaAtkaev if you want to, you can keep your init(name: String....
@Said-AbdullaAtkaev btw, try to fix json as I wrote above
|
2

The most important thing you need is this:

let text = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .text)
texts = try text.allKeys.map { try text.decode(String.self, forKey: $0) }

This will convert the dictionary into an array of values. While in principle JSON values are not ordered, allKeys is ordered (because it's a serialized protocol).

You wouldn't need the encode method if you conformed only to Decodable rather than Codable. Also, most of your optionals are not really optionals.

Putting those things together, you would have this:

class Text: Decodable {

    private enum CodingKeys: String, CodingKey {
        case name, index, count, text
    }

    public var name: String
    public var index: Int
    public var count: Int
    public var texts: [String]

    init(name: String = "",
         index: Int = 0,
         count: Int = 0,
         texts: [String] = []) {
        self.name = name
        self.index = index
        self.count = count
        self.texts = texts
    }

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

        self.name = try container.decode(String.self, forKey: .name)
        self.index = Int(try container.decode(String.self, forKey: .index)) ?? 0
        self.count = Int(try container.decode(String.self, forKey: .count)) ?? 0
        let text = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .text)
        texts = try text.allKeys.map { try text.decode(String.self, forKey: $0) }
    }

}

let jsonObj = try JSONDecoder().decode(Text.self, from: data)

1 Comment

I think this should be a server response fix not a local code

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.