3

My Json:

    {  
   "message":"OK",
   "response":[  
      {  
         "article_id":"201802062200722818",
         "lead":"Poliisi vapautti naisen ja otti miehen kiinni. Satakunnan käräjäoikeus vangitsi miehen tiistaina.",
         "headline":"Poliisi epäilee: 19-vuotias raumalaismies piti alaikäistä naista mökillä vankina",
         "title":"Poliisi epäilee: 19-vuotias raumalaismies piti alaikäistä naista mökillä vankina",
         "service_name":"iltalehti",
         "main_image_name":"cae51c694cc7f31257290ff96489f3cf852329a887509621b29a27fbaa0f8894.jpg",
         "category":{  
            "category_name":"kotimaa",
            "description":"Kotimaan uutiset",
            "parent_category":{  
               "category_name":"uutiset",
               "description":"Uutiset",
               "parent_category":null
            }
         },
         "main_image_urls":{  
            "default":"https://img.ilcdn.fi/PcWFp0weItXN2WAWKBXCO_H2VsQ=/510x/img-s3.ilcdn.fi/cae51c694cc7f31257290ff96489f3cf852329a887509621b29a27fbaa0f8894.jpg",
            "size30":"https://img.ilcdn.fi/_LNHr84u93ntg3tX37oHyGBlRNA=/30x/img-s3.ilcdn.fi/cae51c694cc7f31257290ff96489f3cf852329a887509621b29a27fbaa0f8894.jpg",
            "size98":"https://img.ilcdn.fi/r624bQFqaJ3xqrMScif38JH6SBM=/98x/img-s3.ilcdn.fi/cae51c694cc7f31257290ff96489f3cf852329a887509621b29a27fbaa0f8894.jpg",
            "size138":"https://img.ilcdn.fi/dyemZCdMpjAFTnnD5JiYLh3WGJI=/138x/img-s3.ilcdn.fi/cae51c694cc7f31257290ff96489f3cf852329a887509621b29a27fbaa0f8894.jpg",
            "size244":"https://img.ilcdn.fi/2AiJpLa4oLxEDE0jL_LazhOiTMM=/244x/img-s3.ilcdn.fi/cae51c694cc7f31257290ff96489f3cf852329a887509621b29a27fbaa0f8894.jpg",
            "size293":"https://img.ilcdn.fi/iyAZVQ0ufAHrX2inGCiE9QPQjMU=/293x/img-s3.ilcdn.fi/cae51c694cc7f31257290ff96489f3cf852329a887509621b29a27fbaa0f8894.jpg",
            "size310":"https://img.ilcdn.fi/XGmL7EEqo0OR5Vzvbel1hSeTmHI=/310x/img-s3.ilcdn.fi/cae51c694cc7f31257290ff96489f3cf852329a887509621b29a27fbaa0f8894.jpg",
            "size510":"https://img.ilcdn.fi/PcWFp0weItXN2WAWKBXCO_H2VsQ=/510x/img-s3.ilcdn.fi/cae51c694cc7f31257290ff96489f3cf852329a887509621b29a27fbaa0f8894.jpg",
            "size820":"https://img.ilcdn.fi/N-XV5ZqQASGpvUe-3DAcq4i1928=/820x/img-s3.ilcdn.fi/cae51c694cc7f31257290ff96489f3cf852329a887509621b29a27fbaa0f8894.jpg",
            "size1024":"https://img.ilcdn.fi/J5Cm5P2SJMNymHza7s3LdEEvKLg=/1024x/img-s3.ilcdn.fi/cae51c694cc7f31257290ff96489f3cf852329a887509621b29a27fbaa0f8894.jpg"
         },
         "published_at":"2018-02-06T10:42:40+02:00",
         "updated_at":null
      }
   ]
}

My Codable Model classes

Articles.swift

import Foundation
struct Articles : Codable {
    let message : String?
    let response : [Article]?

    enum CodingKeys: String, CodingKey {

        case message = "message"
        case response = "response"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        message = try values.decodeIfPresent(String.self, forKey: .message)
        response = try values.decodeIfPresent([Article].self, forKey: .response)
    }

}

Article.swift

import Foundation
struct Article : Codable {
    let article_id : String?
    let lead : String?
    let headline : String?
    let title : String?
    let service_name : String?
    let main_image_name : String?
    let category : Category?
    let main_image_urls : Main_image_urls?
    let published_at : String?
    let updated_at : String?

    enum CodingKeys: String, CodingKey {

        case article_id = "article_id"
        case lead = "lead"
        case headline = "headline"
        case title = "title"
        case service_name = "service_name"
        case main_image_name = "main_image_name"
        case category
        case main_image_urls
        case published_at = "published_at"
        case updated_at = "updated_at"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        article_id = try values.decodeIfPresent(String.self, forKey: .article_id)
        lead = try values.decodeIfPresent(String.self, forKey: .lead)
        headline = try values.decodeIfPresent(String.self, forKey: .headline)
        title = try values.decodeIfPresent(String.self, forKey: .title)
        service_name = try values.decodeIfPresent(String.self, forKey: .service_name)
        main_image_name = try values.decodeIfPresent(String.self, forKey: .main_image_name)
        category = try Category(from: decoder)
        main_image_urls = try Main_image_urls(from: decoder)
        published_at = try values.decodeIfPresent(String.self, forKey: .published_at)
        updated_at = try values.decodeIfPresent(String.self, forKey: .updated_at)
    }

}

Category.swift

import Foundation
struct Category : Codable {
    let category_name : String?
    let description : String?
    let parent_category : Parent_category?

    enum CodingKeys: String, CodingKey {

        case category_name = "category_name"
        case description = "description"
        case parent_category
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        category_name = try values.decodeIfPresent(String.self, forKey: .category_name)
        description = try values.decodeIfPresent(String.self, forKey: .description)
        parent_category = try Parent_category(from: decoder)
    }

}

Parent_category.swift

import Foundation
struct Parent_category : Codable {
    let category_name : String?
    let description : String?
    let parent_category : String?

    enum CodingKeys: String, CodingKey {

        case category_name = "category_name"
        case description = "description"
        case parent_category = "parent_category"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        category_name = try values.decodeIfPresent(String.self, forKey: .category_name)
        description = try values.decodeIfPresent(String.self, forKey: .description)
        parent_category = try values.decodeIfPresent(String.self, forKey: .parent_category)
    }

}

Problem:

I am trying to use Codable protocol to initialize my model classes from JSON. It works well for native datatypes (String, Int etc.), But If structure contains custom type property object, it is not initializing the properties of that custom structure (class).

Example: Category is custom type object in Article struct. Every custom class is responsible to initialize its properties, confirms Codable protocol and has its own init(from decoder: Decoder) method.

But somehow category and other custom types are not able to initialize their own properties.(e.g category_name = nil in Category class, and same is happening with Parent_category and Main_image_urls) and I am getting following result: (Some values are nil)

Console log on xcode

po article

▿ Optional<Article>
  ▿ some : Article
    ▿ article_id : Optional<String>
      - some : "201802062200722818"
    ▿ lead : Optional<String>
      - some : "Poliisi vapautti naisen ja otti miehen kiinni. Satakunnan käräjäoikeus vangitsi miehen tiistaina."
    ▿ headline : Optional<String>
      - some : "Poliisi epäilee: 19-vuotias raumalaismies piti alaikäistä naista mökillä vankina"
    ▿ title : Optional<String>
      - some : "Poliisi epäilee: 19-vuotias raumalaismies piti alaikäistä naista mökillä vankina"
    ▿ service_name : Optional<String>
      - some : "iltalehti"
    ▿ main_image_name : Optional<String>
      - some : "cae51c694cc7f31257290ff96489f3cf852329a887509621b29a27fbaa0f8894.jpg"
    ▿ category : Optional<Category>
      ▿ some : Category
        - category_name : nil
        - description : nil
        ▿ parent_category : Optional<Parent_category>
          ▿ some : Parent_category
            - category_name : nil
            - description : nil
            - parent_category : nil
    ▿ main_image_urls : Optional<Main_image_urls>
      ▿ some : Main_image_urls
        - default : nil
        - size30 : nil
        - size98 : nil
        - size138 : nil
        - size244 : nil
        - size293 : nil
        - size310 : nil
        - size510 : nil
        - size820 : nil
        - size1024 : nil
    ▿ published_at : Optional<String>
      - some : "2018-02-06T10:42:40+02:00"
    - updated_at : nil

Am I missing something? Please help guys :-)

Ankush
  • 2,405
  • 3
  • 22
  • 45
  • What happens if you place a breakpoint at category = try Category(from: decoder) in Article.swift? Is there an error you can catch that might tell you why it fails? – rodskagg Feb 06 '18 at 09:48
  • No nothing happens. No error. the line try values.decodeIfPresent(String.self, forKey: .category_name) returns nil in my Category Class's init method – Ankush Feb 06 '18 at 09:50

1 Answers1

2

In Article.swift try change the following lines

category = try Category(from: decoder)
main_image_urls = try Main_image_urls(from: decoder)

to

category = values.decodeIfPresent(Category.self, forKey: .category)
main_image_urls = values.decodeIfPresent(Main_image_urls.self, forKey: .main_image_urls)

Your class Main_image_urls should also conform to codable.

You could also try to omit the init(from decoder: Decoder) methods and let the compiler synthesize it. This also works when having a custom CodingKeys enum.

Update:

Also change the following line in Category.swift

parent_category = try Parent_category(from: decoder)

to

parent_category = values.decodeIfPresent(Parent_category.self, forKey: .parentCategory)
heyfrank
  • 5,291
  • 3
  • 32
  • 46
  • I tried. It throws error Error Error Domain=NSCocoaErrorDomain Code=4864 "Expected to decode String but found a dictionary instead." UserInfo={NSCodingPath=( "IltalehtiToday.Articles.CodingKeys.response", "Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: \"Index 0\", intValue: Optional(0))", "IltalehtiToday.Article.CodingKeys.category", "IltalehtiToday.Parent_category.CodingKeys.parent_category" ), NSDebugDescription=Expected to decode String but found a dictionary instead.} parsing JSON from data (size:2391) – Ankush Feb 06 '18 at 09:57
  • I updated my answer :-). if this doesn't help, try removing the init-functions in every struct. Xcode will generate it itself, since all of your structs are codables. – heyfrank Feb 06 '18 at 10:05
  • Ah Okay It is working now. Thanks. Do you have any idea why is was not working with category = try Category(from: decoder) – Ankush Feb 06 '18 at 10:14
  • Yeah without using `values.decode...` you have inside this function, the decoder doesn't know where (at which key) to look for the specified Object. – heyfrank Feb 06 '18 at 10:18