1

I am trying to parse a large JSON String that I am retrieving from a URL. The JSON I am using to test is below:

let json = """
{
"feed": {
    "title": "Harry Potter",
    "test": "I dont want this value",
    "results": [
        {
        "author": "JK Rowling",
        "artworkURL": "A url",
        "genres": [
            {
                "name": "Fantasy"
            },
            {
                "name": "Scifi"
            }
        ],
            "name": "Goblet of Fire",
            "releaseDate": "2000-07-08"
        },
        {
        "author": "JK Rowling",
        "artworkURL": "A url",
        "genres": [
            {
                "name": "Fantasy"
            },
            {
                "name": "Scifi"
            }
            ],
            "name": "Half Blood Prince",
            "releaseDate": "2009-07-15"
            }
        ]
    }
}
""".data(using: .utf8)!

I have a couple data structs to place the data into:

struct Genre: Decodable {
    let name: String
}

struct Book: Decodable {
    let author: String
    let artworkURL: URL
    let genres: [Genre]
    let name: String
    let releaseDate: String
}

struct BookCollection {
    let title: String
    let books: [Book]

    enum CodingKeys: String, CodingKey {
        case feed
    }

    enum FeedKeys: String, CodingKey {
        case title, results
    }

    enum ResultKeys: String, CodingKey {
        case author, artworkURL, genres, name, releaseDate
    }

    enum GenreKeys: String, CodingKey {
        case name
    }
}

extension BookCollection: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        let feed = try values.nestedContainer(keyedBy: FeedKeys.self, 
    forKey: .feed)
        self.title = try feed.decode(String.self, forKey: .title)
        self.books = try feed.decode([Track].self, forKey: .results)
    }
}

I am then printing off the information like so:

do {
    let response = try JSONDecoder().decode(BookCollection.self, from: json)
    for book in response.books {
        print(book.genres)
    }
} catch {
    print(error)
}

It is successful at printing off all of the information except for the genres. This gives me an array of genres, but I cannot do book.genres.name to access the name. I have to use: book.genres[0] and it gives me results for just the first index.

Is there a way I could perfect my JSON decoding in my BookCollection extension to then utilize book.genres.name?

Thank you

3
  • To be clear, what value book.genres.name should return? Commented Oct 18, 2017 at 22:53
  • @PauloMattos it should return an array for each Book. For the first book it would be Fantasy and Scifi. Commented Oct 18, 2017 at 22:57
  • @DominicPilla you can add a read only computed property to your Book struct extension Book { var allGenres: [String] { return genres.map{$0.name} } } and use print(book.allGenres) Commented Oct 19, 2017 at 0:19

2 Answers 2

1

If you really need that extra name property, you can do so in a new extension:

extension Array where Element == Genre {
    var name: [String] {
        return self.map { $0.name }
    }
}

This adds the aforementioned name property to every [Genre] value out there, including the one defined by your Book type. Just be sure that is really what you are after (if declare this extension as private than it will be available in the corresponding swift file).

Sign up to request clarification or add additional context in comments.

4 Comments

Awesome! That worked. But to confirm there is no way to do this within my BookCollection extension? I added this outside of that extension and it worked just fine.
@DominicPilla No, it isn’t possible. Your really need to extend [Genre] to achieve your goal ;)
I have tried to make extensions for each of my data structs, but I wasn't successful. Are you saying if implemented correctly then it would work?
Swift extension mechanism is quite flexible, it should work! If you have a specific case in mind, I recommend you post another question. BTW, pls don’t forget to mark my answer as accepted when you get a chance ;)
1

To eliminate the needs to use many enum codingKeys and decode your type manually, you can change your data structure to map JSON structure format. Note that the code below, it is not necessary to nest the structs, you can put it parallel as well. This code is tested with your encoded JSON data

public struct HarryPotterFeed: Codable {
  public let feed: BookCollection

  public struct BookCollection: Codable {
    public let title: String
    public let books: [Book]

    // map properties books to json's "results"
    enum CodingKeys: String, CodingKey {
      case title  // needs to come along 
      case books = "results"
    }

    public struct Book: Codable {
      public let author, name, artworkURL, releaseDate : String
      public let genres: [Genre]

      public struct Genre: Codable {
        public let name: String
      }
    }
  }
}

// Decode JSON 

do {
  let response = try JSONDecoder().decode(HarryPotterFeed.self, from: json)
  for book in response.feed.books {
    for name in book.genres {
      print(name)
    }
  }
} catch {
  print("PROBLEM DECODING JSON \(error)")
}

1 Comment

Thank you for your comment. The JSON data structure had multiple keys that were not necessary to have, and would have made my data structures filled with useless values!

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.