This question already has answers here:
Can we reuse struct on Swift? Or is there any other way?
(4 answers)
Closed 1 year ago.
Assuming I have two API calls
// /someurl/api/product-list
{
"status":0,
"message": "ok",
"data":{
"items":[
{
"id":1,
"name":"iPhone",
"desc":{
"en-us":"This is an iPhone",
"fr":"Ceci est un iPhone"
}
}
]
}
}
// /someurl/api/movie-list
{
"status":0,
"message": "ok",
"data":{
"items":[
{
"imdb_id":"tt0081505",
"title":"The Shining",
"short_desc":{
"en-us":"This is The Shining",
"fr":"C'est le Shining"
}
}
]
}
}
Now, both two api responses include the same structure of status, message(potentially would have pagination info object), except the data are different.
And In productModel
struct ProcuctAPIResponse: Codable {
let status: Int
let data: ProcuctAPIResponseData
let message: String?
}
struct ProcuctAPIResponseData: Codable {
let items: [Product]
}
struct Product: Codable {
let id: Int
let name: String?
let desc: [String: String]?
}
And In movieModel
struct MovieAPIResponse: Codable {
let status: Int
let data: MovieAPIResponseData
let message: String?
}
struct MovieAPIResponseData: Codable {
let items: [Movie]
}
struct Movie: Codable {
let imdb_id: Int
let title: String?
let short_desc: [String: String]?
}
My question is that is there a way I can create a BaseAPIResponse
something like
struct BaseAPIResponse: Codable {
let status: Int
let message: String?
}
then ProcuctAPIResponse and MovieAPIResponse can be extended from of it?
If not, how do you optimize these codes?
use protocols
protocol BaseAPIResponse: Codable {
let status: Int
let message: String?
}
Now implement the structs:
struct ProcuctAPIResponse: BaseAPIResponse {...}
movieModel
struct movieModel: BaseAPIResponse {...}
Related
I have a JSON response from my api that returns this:
[
{
"id": 1,
"chapter": 5,
"amount": 28,
"texts": [
{
"lyric": "lorem ipsum",
"number": 1
},
{
"lyric": "lorem foo bar",
"number": 2
}
],
"book": 1
}
]
I tried
struct Chapter: Decodable, Identifiable {
var id: Int
var chapter: Int
var amount: Int
struct Lyrics: Codable {
var lyricText: String
var lyricNumber: Int
}
enum Codingkeys: String, CodingKey {
case lyricText = "lyric"
case lyricNumber = "number"
}
}
But I get the following error upon making the call
dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))
My API call looks like this:
...
#Published var chapters = [Chapter]()
func fetchBookDetails() {
if let url = URL(string: url) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error == nil {
if let safeData = data {
do {
let response = try JSONDecoder().decode([Chapter].self, from: safeData)
DispatchQueue.main.async {
self.chapters = response
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
The struct looks fine I guess, but the api call is complaining - any idea what it could be? Or is it the struct that is done incorrectly
texts is a sub structure (an array of properties), so you need to define a second container for it, for example
struct Text: Codable {
let lyric: String
let number: Int
}
Then you can update Chapter to reference the sub structure something like...
struct Chapter: Decodable {
let id: Int
let chapter: Int
let amount: Int
let book: Int
let texts: [Text]
}
And finally, load it...
let chapters = try JSONDecoder().decode([Chapter].self, from: jsonData)
But what about the error message?
dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))
Oh, right, but the error message is telling there is something wrong with what you've downloaded. I like to, in these cases, convert the data to String and print it if possible, that way, you know what is been returned to you.
For example:
let actualText = String(data: safeData, encoding: .utf8)
The print this and see what you're actually getting
The Playground test code
import UIKit
let jsonText = """
[
{
"id": 1,
"chapter": 5,
"amount": 28,
"texts": [
{
"lyric": "lorem ipsum",
"number": 1
},
{
"lyric": "lorem foo bar",
"number": 2
},
],
"book": 1
}
]
"""
struct Text: Codable {
let lyric: String
let number: Int
}
struct Chapter: Decodable {
let id: Int
let chapter: Int
let amount: Int
let book: Int
let texts: [Text]
}
let jsonData = jsonText.data(using: .utf8)!
do {
let chapters = try JSONDecoder().decode([Chapter].self, from: jsonData)
} catch let error {
error
}
I have a json that one part of it is not constant and I don't know how to do the Codable struct.
Please see the example code under:
The fruits part is not constant so I'm not sure how to do the Codable struct. I tried to find answer on SOF but I can't find any for "non constant json Codable struct". I might not searching the correct key words.
Thank you.
Here is the example of the json:
{
"success": true,
"username": "app",
"data": {
"locations": {
"asia": {
"japan": {
"store_count": 5
},
"korea": {
"store_count": 3
}
}
},
"market": {
"fruits": {
"banana": {
"price": 50.00,
"count": 2
},
"apple": {
"price": 444.00,
"count": 16
},
"mango": {
"price": 28.00,
"count": 1
},
"peach": {
"price": 50.00,
"count": 2
},
"watermelon": {
"price": 50.00,
"count": 2
},
"blackberry": {
"price": 57.00,
"count": 2
}
}
}
}
Struct for the json
struct Markets: Codable {
let success: Bool?
let data: Data?
struct Data: Codable {
let locations: Locations
let market: Market
struct Locations: Codable {
let asia: Asia
struct Asia: Codable {
let japan: Stores
ler korea:
}
struct Stores: Codable {
let store_count: Int
}
}
struct Market: Codable {
var fruits: Type
struct Type: Codable {
// the fruits type are not constant.
}
}
}
}
}
Define the struct that is consistent:
struct FruitInfo: Codable {
var price: Double
var count: Int
}
And map those to String:
var fruits: [String: FruitInfo]
There are many other solutions if you want another type of structure (for example embedding the name of the fruit into the struct), but this is the simplest, and requires no custom init.
import Foundation
// MARK: - Test
struct Test: Codable {
let success: Bool
let username: String
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let locations: Locations
let market: Market
}
// MARK: - Locations
struct Locations: Codable {
let asia: Asia
}
// MARK: - Asia
struct Asia: Codable {
let japan, korea: Japan
}
// MARK: - Japan
struct Japan: Codable {
let storeCount: Int
enum CodingKeys: String, CodingKey {
case storeCount = "store_count"
}
}
// MARK: - Market
struct Market: Codable {
let fruits: Fruits
}
// MARK: - Fruits
struct Fruits: Codable {
let banana, apple, mango, peach: Apple
let watermelon, blackberry: Apple
}
// MARK: - Apple
struct Apple: Codable {
let price, count: Int
}
from https://app.quicktype.io/
simply enter your json and it gives you either class or struct
I have a problem with decoding JSON from API.
I get JSON:
{
"meta": {
"count": 1,
"links": {
"self": "https://test.api.amadeus.com/v1/reference-data/locations?subType=AIRPORT&keyword=barcel&sort=analytics.travelers.score&view=LIGHT&page%5Boffset%5D=0&page%5Blimit%5D=10"
}
},
"data": [
{
"type": "location",
"subType": "AIRPORT",
"name": "AIRPORT",
"detailedName": "BARCELONA/ES:AIRPORT",
"id": "ABCN",
"self": {
"href": "https://test.api.amadeus.com/v1/reference-data/locations/ABCN",
"methods": [
"GET"
]
},
"iataCode": "BCN",
"address": {
"cityName": "BARCELONA",
"countryName": "SPAIN"
}
}
]
}
My code looks like this:
struct Airport: Decodable {
let meta: MetaAirport
let data: AirportInfo
}
struct Address: Decodable{
let countryName: String
let cityName: String
}
struct AirportInfo: Decodable{
let address: Address
let subType: String
let id: String
let type: String
let detailedName: String
let name: String
let iataCode: String
}
struct MetaAirport : Decodable{
let count: Int
let links: Links
}
struct Links : Decodable{
let info: String
private enum CodingKeys : String, CodingKey {
case info = "self"
}
}
I'm trying to decode json using JSONDecoder on an AirportInfo object
do {
let airport = try JSONDecoder().decode(Airport.self, from: data!)
print(airport.data.name)
}
catch let err {
print(err)
}
I get an error
typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil)], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))
What am I doing wrong?
In json data, address is a object. Change your code:
let address: [Address] -> let address: Address
Your Model should be like that to parse
struct DataModel: Codable {
let meta: Meta?
let data: [Datum]?
}
struct Datum: Codable {
let type, subType, name, detailedName: String?
let id: String?
let datumSelf: SelfClass?
let iataCode: String?
let address: Address?
enum CodingKeys: String, CodingKey {
case type, subType, name, detailedName, id
case datumSelf = "self"
case iataCode, address
}
}
struct Address: Codable {
let cityName, countryName: String?
}
struct SelfClass: Codable {
let href: String?
let methods: [String]?
}
struct Meta: Codable {
let count: Int?
let links: Links?
}
struct Links: Codable {
let linksSelf: String?
enum CodingKeys: String, CodingKey {
case linksSelf = "self"
}
}
I see you have edited your question from the original, so it feels hard to correctly identify at a given moment in time where the issue is.
However, Codable is great and one easy way for you to work through it is by constructing your object little by little. There is no requirement for an object to identically represent your JSON IF you make things optional. So when debugging this kind of issues, make different properties optional and see what got parsed. This would allow you to identify little by little where you're doing things wrong, without causing crashes and keeping the correctly formatted parts.
Also, pro tip: If you plan to keep this code for anything more than 1 week, stop using force unwrapping ! and get used to handle optionals correctly. Otherwise this is a hard habit to stop and others will hate the code you've created ;)
currently my project uses structure MVVM. I have a Pagination JSON like this:
{
"has_next": false,
"next_params": {
"limit": 10,
"offset": 10
},
"results": [
{ "id": 1, "name": "A Place" },
{ "id": 2, "name": "A Night" }
]
}
This is my ViewModel:
class LifeStoryViewModel: ObservableObject {
#Published var lifes: [Life] = []
var has_next: Bool = true
var next_params: [String:Any] = [:]
var fetching: Bool = false
func fetchLifeStories () {
let url = URL(string: STRINGURL)
URLSession.shared.dataTask(with: url!) { (data, res, err) in
DispatchQueue.main.async {
let vvv = try! JSONDecoder().decode(LifeStories.self, from: data!)
self.lifes = vvv.results
}
}.resume()
}
}
As you guys see, I have a model LifeStories:
struct Life: Identifiable, Codable {
var id: Int
var name: String
var description: String
var thumbnail: String
}
struct LifeStories: Codable {
var has_next: Bool
var results: [Life]
}
Can I remove LifeStories model and handle it inside the LifeStoryViewModel instead? How can I do this because I think LifeStories model is not necessary.
You can use a generic type for paginated responses. Here's an example from the app I'm working on:
struct PaginatedResponse<Element: Codable>: Codable {
var count: Int
var next: String?
var previous: String?
var results: [Element]
}
A few months back I started learning how to use the new codable and I posted this question Swift 4 decoding json using Codable. Right now I'm trying to use Realm and I've changed the model to follow the documents but I keep getting the error that it doesn't conform to decodable. I'm pretty lost as to how to make this work.
{
"success": true,
"message": "got the locations!",
"data": {
"LocationList": [
{
"LocID": 1,
"LocName": "Downtown"
},
{
"LocID": 2,
"LocName": "Uptown"
},
{
"LocID": 3,
"LocName": "Midtown"
}
]
}
}
class Location: Object, Decodable {
#objc dynamic var success: Bool = true
let data = List<LocationData>()
}
class LocationData: Object, Decodable {
let LocationList = List<LocationItem>()
}
class LocationItem: Object, Decodable {
#objc dynamic var LocID: Int!
#objc dynamic var LocName: String!
}
Instead of declaring your lists like this:
let data = List<LocationData>()
let LocationList = List<LocationItem>()
Declare them like this instead:
var data = LocationData()
var LocationList = [LocationItem]()
This confirms to the codable/decodable
Update:
I made a test which worked out with your models, tryout this:
struct Location: Decodable {
let success: Bool
let message: String
var data = LocationData()
}
struct LocationData: Decodable {
var LocationList = [LocationItem]()
}
struct LocationItem: Decodable {
var LocID: Int
var LocName: String
}
let json = "{ \"success\": true, \"message\": \"got the locations!\", \"data\": { \"LocationList\": [ { \"LocID\": 1, \"LocName\": \"Downtown\" }, { \"LocID\": 2, \"LocName\": \"Uptown\" }, { \"LocID\": 3, \"LocName\": \"Midtown\" } ] } }"
do {
let data = Data(json.utf8)
let decoded = try JSONDecoder().decode(Location.self, from: data)
print(decoded)
} catch let error {
print(error)
}
This will output:
Location(success: true, message: "got the locations!", data: CodableDecodable.LocationData(LocationList: [CodableDecodable.LocationItem(LocID: 1, LocName: "Downtown"), CodableDecodable.LocationItem(LocID: 2, LocName: "Uptown"), CodableDecodable.LocationItem(LocID: 3, LocName: "Midtown")]))
In your case your models should be:
class Location: Object, Decodable {
#objc dynamic var success: Bool = true
var data = LocationData()
}
class LocationData: Object, Decodable {
var LocationList = [LocationItem]()
}
class LocationItem: Object, Decodable {
#objc dynamic var LocID: Int!
#objc dynamic var LocName: String!
}