jsonswiftswift4codablecustom-properties

Codable custom class type property can not initialize its own properties from JSON


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 :-)


Solution

  • 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)