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 callsJSONSerialization.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
Initializer for Conditional Binding Must Have Optional Type, Not 'String'
Working with C Strings in Swift, Or: How to Convert Unsafepointer<Cchar> to Cstring
Swift 3.0 Iterate Over String.Index Range
How to Clear Alamofireimage Setimagewithurl Cache
Swiftui Set Position to Center of Different View
Grab Frames from Video Using Swift
Getting Country Name from Country Code
Swiftui: Prevent Image() from Expanding View Rect Outside of Screen Bounds
Swift Error: Binary Operator '&&' Cannot Be Applied to Two 'Bool' Operands
Private Var Is Accessible from Outside the Class
"Cannot Assign Value of Type 'String' to Type 'Anyobject'", Swift 3, Xcode 8 Beta 6
How to Limit the Movement of Two Anchored Lines So They Swing Continually Like a Pendulum
Converting Swift Array to Cfarray in Xcode 8 (Swift 3)
Get Build Date and Time in Swift
How to Use Uiwindowscene.Windows on iOS 15
How Constant Is the Firebase Anonymous Id
In Swiftui, Where Are the Control Events, I.E. Scrollviewdidscroll to Detect the Bottom of List Data