4

I'm new in iOS development, so sorry for stupid question in advance. I have json like this:

{
   "type":"post",
   "comments":{
      "count":0,
      "can_post":1
   },
   "likes":{
      "count":0,
      "user_likes":0,
      "can_like":1,
      "can_publish":1
   },
   "reposts":{
      "count":0,
      "user_reposted":0
   }
}

I want to convert this to class which will contain just likesCount, commentsCount, repostsCount but without creating separate classes for comments, likes, reposts. I'm using Decodable for this and here is my code which doesn't work :)

Code:

final class FeedItem: Decodable {
    enum Keys: String, CodingKey {
        case type,
        likes = "likes.count",
        comments = "comments.count",
        reposts = "reposts.count"
    }

    let type: String
    var likes = 0
    var comments = 0
    var reposts = 0

    required convenience init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Keys.self)
        let type = try container.decode(String.self, forKey: .type)
        let likes = try container.decode(Int.self, forKey: .likes)
        let comments = try container.decode(Int.self, forKey: .comments)
        let reposts = try container.decode(Int.self, forKey: .reposts)
        self.init(type: type, likes: likes, comments: comments, reposts: reposts)
    }

    init(type: String,
         likes: Int,
         comments: Int,
         reposts: Int) {
        self.type = type
        self.likes = likes
        self.comments = comments
        self.reposts = reposts
    }
}

Error:

"No value associated with key Keys(stringValue: \"likes.count\", intValue: nil) (\"likes.count\")."
Dan Loewenherz
  • 10,879
  • 7
  • 50
  • 81
  • Use traditional `JSONSerialization`. It's less effort (and more efficient) than *fighting* `Codable` – vadian Nov 10 '18 at 18:19

1 Answers1

3

The error is very clear, there are no values for associate keys likes.count as the key not exists.

Use

let container = try decoder.container(keyedBy: CodingKeys.self)

and try container.decodeIfPresent(:_) to check for key exists or if not assign empty value.

Code:

struct FeedItem: Codable {

    let type: String
    let commentsCount: Int
    let canPostComment: Int
    let likesCount: Int
    let userLikes: Int
    let canLike: Int
    let canPublish: Int
    let repostsCount: Int
    let userReposted: Int

    enum CodingKeys: String, CodingKey {
        case type = "type"
        case comments = "comments"
        case likes = "likes"
        case reposts = "reposts"
        case count = "count"
        case canPost = "can_post"
        case userLikes = "user_likes"
        case canLike = "can_like"
        case canPublish = "can_publish"
        case userReposted = "user_reposted"
    }

    init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.type = try container.decodeIfPresent(String.self, forKey: .type) ?? ""

        let comments = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .comments)
        self.commentsCount = try comments.decodeIfPresent(Int.self, forKey: .count) ?? 0
        self.canPostComment = try comments.decodeIfPresent(Int.self, forKey: .canPost) ?? 0

        let likes = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .likes)
        self.likesCount = try likes.decodeIfPresent(Int.self, forKey: .count) ?? 0
        self.userLikes = try likes.decodeIfPresent(Int.self, forKey: .userLikes) ?? 0
        self.canLike = try likes.decodeIfPresent(Int.self, forKey: .canLike) ?? 0
        self.canPublish = try likes.decodeIfPresent(Int.self, forKey: .canPublish) ?? 0

        let reposts = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .reposts)
        self.repostsCount = try reposts.decodeIfPresent(Int.self, forKey: .count) ?? 0
        self.userReposted = try reposts.decodeIfPresent(Int.self, forKey: .userReposted) ?? 0
    }

    func encode(to encoder: Encoder) throws {

        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(type, forKey: .type)

        var comments = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .comments)
        try comments.encode(commentsCount, forKey: .count)
        try comments.encode(canPostComment, forKey: .canPost)

        var likes = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .likes)
        try likes.encode(likesCount, forKey: .count)
        try likes.encode(userLikes, forKey: .userLikes)
        try likes.encode(canLike, forKey: .canLike)
        try likes.encode(canPublish, forKey: .canPublish)

        var reposts = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .reposts)
        try reposts.encode(repostsCount, forKey: .count)
        try reposts.encode(userReposted, forKey: .userReposted)
    }
}

Data Reading:

let data = //Your JSON data from API
let jsonData = try JSONDecoder().decode(FeedItem.self, from: data)
print("\(jsonData.type) \(jsonData.canLike)")
Sateesh Yemireddi
  • 4,289
  • 1
  • 20
  • 37