Swift 4 Decode Simple Root Level JSON Value

Swift 4 decode simple root level json value

It works with good ol' JSONSerialization and the .allowFragments
reading option. From the documentation:

allowFragments

Specifies that the parser should allow top-level objects that are not an instance of NSArray or NSDictionary.

Example:

let json = "22".data(using: .utf8)!

if let value = (try? JSONSerialization.jsonObject(with: json, options: .allowFragments)) as? Int {
print(value) // 22
}

However, JSONDecoder has no such option and does not accept top-level
objects which are not arrays or dictionaries. One can see in the
source code that the decode() method calls
JSONSerialization.jsonObject() without any option:

open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
let topLevel: Any
do {
topLevel = try JSONSerialization.jsonObject(with: data)
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
}

// ...

return value
}

Swift 4 Codable; How to decode object with single root-level key

You could decode using a dictionary: user combination then extract out the user object. e.g.

struct User: Codable {
let id: Int
let username: String
}

let decoder = JSONDecoder()
let userDictionary = try decoder.decode([String: User].self, from: jsonData)

Decoding JSON to Swift Model Not Root Level

Using JSONDecoder alone, you cannot decode without some sort of outer struct, because your result is going to be an array of Article, which is an outer entity. Merely defining Article alone thus can never be sufficient.

If you dislike declaring an outer struct that you don't need for any other purpose than to drill down to the "articles" key, that is easily solved by declaring it only temporarily within the limited scope where you do drill down to the "articles" key. The rest of your program is thus left with the Article struct but the outer struct doesn't exist there.

For example:

struct Article: Decodable {
// ... your code here ...
}
func getArticles(_ d:Data) -> [Article] {
struct Articles: Decodable { // this struct is temporary
let articles:[Article]
}
return try! JSONDecoder().decode(Articles.self, from: d).articles
}

Other code can now see the Article struct and can call getArticles to parse the JSON and receive the array of Article, but other code never knows (and can never find out) that an extra Articles struct exist; it exists only temporarily within the getArticles function as a kind of local. It is no more objectionable than any other local variable created temporarily within a function body.

Decode in Swift a JSON response to an array of struct using top level data

You might be able to do so with a custom init(decoder:), but another way is to hide the internal implementation where you stick to the JSON Model, and use lazy var or computed get. Lazy var will be load only once, it depends if you keep or not the root.

struct Root: Codable {
//Hidde,
private let data: RootData

//Hidden
struct RootData: Codable {
let author_id: String
let author_name: String

let language: String

let books: [RootBook]
}
//Hidden
struct RootBook: Codable {
let book_id: String
let book_name: String
}

lazy var books: [Book] = {
let author = Author(id: data.author_id, name: data.author_name)
return data.books.map {
Book(id: $0.book_id, name: $0.book_name, author: author, language: data.language)
}
}()

var books2: [Book] {
let author = Author(id: data.author_id, name: data.author_name)
return data.books.map {
Book(id: $0.book_id, name: $0.book_name, author: author, language: data.language)
}
}
}

//Visible
struct Book: Codable {
let id: String
let name: String
let author: Author
let language: String
}
//Visible
struct Author: Codable {
let id: String
let name: String
}

Use:

do {
var root = try JSONDecoder().decode(Root.self, from: jsonData)
print(root)
print("Books: \(root.books)") //Since it's a lazy var, it need to be mutable
} catch {
print("Error: \(error)")
}

or

do {
let root = try JSONDecoder().decode(Root.self, from: jsonData)
print(root)
print("Books: \(root.books2)")
} catch {
print("Error: \(error)")
}

Side note: The easiest way is to stick to the JSON Model indeed. Now, it might be interesting also to have internal model, meaning, your have your own Book Class, that you init from Root. Because tomorrow, the JSON might change (change of server, etc.). Then the model used for your views (how to show them) might also be different...
Separate your layers, wether you want to use MVC, MVVM, VIPER, etc.

EDIT:

You can with an override of init(decoder:), but does it make the code clearer? I found it more difficult to write than the previous version (meaning, harder to debug/modify?)

struct Root2: Codable {
let books: [Book2]

private enum TopLevelKeys: String, CodingKey {
case data
}
private enum SubLevelKeys: String, CodingKey {
case books
case authorId = "author_id"
case authorName = "author_name"
case language
}
private enum BoooKeys: String, CodingKey {
case id = "book_id"
case name = "book_name"
}
init(from decoder: Decoder) throws {
let topContainer = try decoder.container(keyedBy: TopLevelKeys.self)
let subcontainer = try topContainer.nestedContainer(keyedBy: SubLevelKeys.self, forKey: .data)
var bookContainer = try subcontainer.nestedUnkeyedContainer(forKey: .books)
var books: [Book2] = []
let authorName = try subcontainer.decode(String.self, forKey: .authorName)
let authorid = try subcontainer.decode(String.self, forKey: .authorId)
let author = Author(id: authorid, name: authorName)
let language = try subcontainer.decode(String.self, forKey: .language)
while !bookContainer.isAtEnd {
let booksubcontainer = try bookContainer.nestedContainer(keyedBy: BoooKeys.self)
let bookName = try booksubcontainer.decode(String.self, forKey: .name)
let bookId = try booksubcontainer.decode(String.self, forKey: .id)
books.append(Book2(book_id: bookId, book_name: bookName, author: author, language: language))
}
self.books = books
}
}
struct Book2: Codable {
let book_id: String
let book_name: String
let author: Author
let language: String
}

How to correctly parse JSON with root element as an array in Swift 4?

This is really an awkwardly structured JSON. If you can't get the sender to change the format, here's my attempt at parsing it using JSONDecoder:

typealias Response = [Entry]

struct Entry: Codable {
let stores: [StoreEntry]?
let products: [ProductEntry]?
}

struct ProductEntry: Codable {
let product: Product
}

struct Product: Codable {
let name, price: String
}

struct StoreEntry: Codable {
let store: Store
}

struct Store: Codable {
let name, city: String

enum CodingKeys: String, CodingKey {
case name = "Name"
case city = "City"
}
}

let response = try JSONDecoder().decode(Response.self, from: data)
let stores = response.flatMap { $0.stores.map { $0.map { $0.store } } }.flatMap { $0 }
let products = response.flatMap { $0.products.map { $0.map { $0.product } } }.flatMap { $0 }

Swift Json how to decode with no top level key and autogenerated keys

Since you need to have the id it should be a property of Room

struct Room: Decodable {
var id: Int?
let name: String
let description: String
}

With this we can decode the json as a dictionary of [String: Room] and use map to assign the right id to each room

do {
let dictionary = try JSONDecoder().decode([String: Room].self, from: data)
let rooms = dictionary.map { tuple -> Room in
var room = tuple.value
room.id = Int(tuple.key)
return room
}
print(rooms)
} catch {
print(error)
}

If you don't want to make id optional you can decode it as a dictionary of dictionaries and create Room object when mapping

do {
let dictionary = try JSONDecoder().decode([String: [String: String]].self, from: data)
let rooms = dictionary.compactMap { tuple -> Room? in
guard let id = Int(tuple.key), let name = tuple.value["name"], let description = tuple.value["description"] else {
return nil
}
return Room(id: id, name: name, description: description)
}
print(rooms)
} catch {
print(error)
}

How to decode variable from json when key is changing according to user input?

You can decode the dynamic key by creating a custom init(from:) method for your struct, then using two set of coding keys, an enum containing all keys that are known at compile time and another struct that you initialize using the dynamic keys that are generated using user input (contain the name of the currency).

In your custom init(from:) method you just need to decode each property using their respective keys.

let chosenCurrency = "gbp"

struct CurrencyResponse: Decodable {
let name:String
let symbol:String
let price:String
private static var priceKey:String {
return "price_\(chosenCurrency)"
}

private enum SimpleCodingKeys: String, CodingKey {
case name, symbol
}

private struct PriceCodingKey : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}

init(from decoder:Decoder) throws {
let values = try decoder.container(keyedBy: SimpleCodingKeys.self)
name = try values.decode(String.self, forKey: .name)
symbol = try values.decode(String.self, forKey: .symbol)
let priceValue = try decoder.container(keyedBy: PriceCodingKey.self)
price = try priceValue.decode(String.self, forKey: PriceCodingKey(stringValue:CurrencyResponse.priceKey)!)
}
}

do {
let cryptoCurrencies = try JSONDecoder().decode([CurrencyResponse].self, from: priceJSON.data(using: .utf8)!)
} catch {
print(error)
}

Test JSON:

let priceJSON = """
[
{
"id": "bitcoin",
"name": "Bitcoin",
"symbol": "BTC",
"rank": "1",
"price_\(chosenCurrency)": "573.137",
"price_btc": "1.0",
"24h_volume_\(chosenCurrency)": "72855700.0",
"market_cap_\(chosenCurrency)": "9080883500.0",
"available_supply": "15844176.0",
"total_supply": "15844176.0",
"percent_change_1h": "0.04",
"percent_change_24h": "-0.3",
"percent_change_7d": "-0.57",
"last_updated": "1472762067"
},
{
"id": "ethereum",
"name": "Ethereum",
"symbol": "ETH",
"rank": "2",
"price_\(chosenCurrency)": "12.1844",
"price_btc": "0.021262",
"24h_volume_\(chosenCurrency)": "24085900.0",
"market_cap_\(chosenCurrency)": "1018098455.0",
"available_supply": "83557537.0",
"total_supply": "83557537.0",
"percent_change_1h": "-0.58",
"percent_change_24h": "6.34",
"percent_change_7d": "8.59",
"last_updated": "1472762062"
}
]
"""

How to decode a nested JSON struct with Swift Decodable protocol?

Another approach is to create an intermediate model that closely matches the JSON (with the help of a tool like quicktype.io), let Swift generate the methods to decode it, and then pick off the pieces that you want in your final data model:

// snake_case to match the JSON and hence no need to write CodingKey enums
fileprivate struct RawServerResponse: Decodable {
struct User: Decodable {
var user_name: String
var real_info: UserRealInfo
}

struct UserRealInfo: Decodable {
var full_name: String
}

struct Review: Decodable {
var count: Int
}

var id: Int
var user: User
var reviews_count: [Review]
}

struct ServerResponse: Decodable {
var id: String
var username: String
var fullName: String
var reviewCount: Int

init(from decoder: Decoder) throws {
let rawResponse = try RawServerResponse(from: decoder)

// Now you can pick items that are important to your data model,
// conveniently decoded into a Swift structure
id = String(rawResponse.id)
username = rawResponse.user.user_name
fullName = rawResponse.user.real_info.full_name
reviewCount = rawResponse.reviews_count.first!.count
}
}

This also allows you to easily iterate through reviews_count, should it contain more than 1 value in the future.



Related Topics



Leave a reply



Submit