I'm trying to access a nested array in my SwiftUI code.
I have an array that looks like this:
{
"Count": 123,
"someKey": [
{
"Id": 12345,
"Images": [
28008,
28009
]
}
]
}
I'm trying to access the Images. So I came up with this code:
#ObservedObject var fetcherP = FetcherP()
public class FetcherP: ObservableObject {
#Published var sightingsP = [someKeyP]()
init(){
load()
}
func load() {
URLSession.shared.dataTask(with: request) { [self](data,response,error) in
do {
if let d = data {
let res = try JSONDecoder().decode(RootP.self, from: d)
DispatchQueue.main.async {
self.sightingsP = res.sightingsP
}
}else {
print("No Data")
}
} catch {
print (error)
}
}.resume()
}
}
and my codable:
struct RootP: Codable {
let count: Int
let someKeyP: [someKeyP]
enum CodingKeys: String, CodingKey {
case count = "count"
case someKeyP = "someKey"
}
}
struct someKeyP: Codable, Identifiable {
public var id: Int
public var images: [Int]
enum CodingKeys: String, CodingKey {
case id = "id"
case images = "images"
}
}
And this is how I try to use my code:
ForEach(fetcherP.sightingsP) {myimages in
**//How can I access the images here? I can't do foreach(myimages.images) {img in{
Text(img)
}**
}
Do I have to use double foreach? or is there any other way to do this?
any pointers would be appreciated.
There are a few things going wrong:
Your CodingKeys don't reflect the JSON -- case sensitivity matters
In ForEach, unless your model conforms to Hashable, you need to provide an id for it to use.
You can't provide an Int to Text -- you have to provide a String (I used string interpolation using "\(intValue)")
let jsonData = """
{
"Count": 123,
"someKey": [
{
"Id": 12345,
"Images": [
28008,
28009
]
}
]
}
""".data(using: .utf8)!
public class FetcherP: ObservableObject {
#Published var sightingsP = [someKeyP]()
init(){
load()
}
func load() {
do {
sightingsP = (try JSONDecoder().decode(RootP.self, from: jsonData)).someKeyP
} catch {
print(error)
}
}
}
struct RootP: Codable {
let count: Int
let someKeyP: [someKeyP]
enum CodingKeys: String, CodingKey {
case count = "Count"
case someKeyP = "someKey"
}
}
struct someKeyP: Codable, Identifiable {
public var id: Int
public var images: [Int]
enum CodingKeys: String, CodingKey {
case id = "Id"
case images = "Images"
}
}
struct ContentView : View {
#ObservedObject var fetcherP = FetcherP()
var body: some View {
VStack {
ForEach(fetcherP.sightingsP, id: \.id) { myImages in
ForEach(myImages.images, id: \.self) { imageNumber in
Text("\(imageNumber)")
}
}
}
}
}
Note that what I've included here is a complete, reproducible example -- it can be copied and pasted into Xcode and tested.
Related
I had data struct like this
{
"version": 1,
"profile": [
{
"type": "name",
"value": "Hellow"
},
{
"type": "email",
"value": "1#a.com"
},
]
}
Now I could decode it like this
struct Profile: Decodable {
let version: Int
let name: String
let email: String
struct Item: Decodable {
let type: String
let value: String
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
version = try container.decode(Int.self, forKey: .version)
let items = try container.decode([Item].self, forKey: .data)
name = items.first{$0.type == "name"}!.value
email = items.first{$0.type == "email"}!.value
}
}
Does there are any way to update Profile without rewrite init(from)?
Update
#dynamicMemberLookup can use like mapping function.
But it will lose autocomplete.
#dynamicMemberLookup
struct Profile: Decodable {
let version: Int
struct Item: Decodable {
let type: Keys
let value: String
}
let profile: [Item]
enum Keys: String, Decodable, ExpressibleByStringLiteral {
case name
case email
case undefined
init(stringLiteral: String) {
self = Keys(rawValue: stringLiteral) ?? .undefined
}
}
subscript(dynamicMember member: Keys) -> String {
return profile.first{$0.type == member}?.value ?? "undefined"
}
}
I imported json manually (json file). I get this error when I decode the json.
The data couldn’t be read because it isn’t in the correct format.
where am I doing wrong? Is my model wrong? Is my reference wrong?
JSON:
{
"allQuiz": [
{
"title":"Ağustos Test 1",
"test": [
{
"id": 1,
"question":"Şekle göre aşağıdakiler hangisi doğrudur",
"isQuestionImage": true,
"isSectionImage": false,
"imageName":"1.png",
"sections": {
"A":"2 numaralı aracın öncelikle geçmesi",
"B":"1 numaralı aracın hızını arttırarak kavşağa girmesi",
"C":"2 numaralı aracın 3 numaralı aracın geçmesini beklemesi",
"D":"3 numaralı aracın 2 numaralı aracı ikaz ederek durdurması"
},
"correct": "A"
}
]
}
]
}
Model:
struct QuizContainer: Codable {
var allQuiz: [Quiz]
}
struct Quiz: Codable {
var title: String
var test: [Test]
}
enum TestSectionType: String, Codable, Hashable {
case A = "A"
case B = "B"
case C = "C"
case D = "D"
}
struct Test: Codable {
var id: Int
var question: String
var isQuestionImage: Bool
var isSectionImage: Bool
var imageName: String
var sections: [TestSectionType.RawValue : String]
var correct: String
}
JSON Decode:
func getQuizQuestion() {
let databaseReference = Database.database().reference().child("allQuiz")
databaseReference.observeSingleEvent(of: .value) { snapshot in
do {
let foo = try FirebaseDecoder().decode(QuizContainer.self, from: snapshot.value ?? "")
print(foo)
} catch {
print(error.localizedDescription)
}
}
}
Could the getQuizQuestion function be faulty?
I solved my problem.
I don't need reference.
func getQuizQuestion() {
let databaseReference = Database.database().reference() // here
databaseReference.observeSingleEvent(of: .value) { snapshot in
do {
let foo = try FirebaseDecoder().decode(QuizContainer.self, from: snapshot.value ?? "")
print(foo)
} catch {
print(error.localizedDescription)
}
}
}
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]
}
I have a following struct with generics for API response with pagination:
struct Paginable<Body> {
let data: [Body]
let meta: Meta
}
extension Paginable {
struct Meta: Codable {
let pagination: Pagination
struct Pagination: Codable {
let total: Int
let count: Int
let perPage: Int
let currentPage: Int
let totalPages: Int
}
}
}
And I want to be able to decode it like so:
let response = try? response.decode(to: Paginable<Segment>.self)
So here's my attempt to make it Decodable:
extension Paginable where Body == Data {
func decode<BodyType: Decodable>(to type: BodyType.Type) throws -> Paginable<BodyType> {
guard let decodedJSON = try? JSONDecoder().decode(BodyType.self, from: data) else {
throw APIError.decodingFailure
}
return Paginable<BodyType>(data: decodedJSON, meta: self.meta)
}
}
This gives me two errors:
Cannot convert value of type 'Paginable.Meta' to expected argument type 'Paginable<_>.Meta'
on the line with return statement
If I change the meta property to some primitive type like Int, the error disappears. But Meta itself is Codable, so what's to problem here?
Cannot convert value of type '[Data]' to expected argument type 'Data'
on the line with guard statement
How to solve this one?
You need to conform Paginable to Codable like,
struct Paginable<Body>: Codable where Body: Codable {
let data: [Body]
let meta: Meta
}
Change decode(data:to:) method in extension Paginable to,
extension Paginable {
static func decode<BodyType: Decodable>(data: Data, to type: BodyType.Type) throws -> BodyType? {
do {
let response = try JSONDecoder().decode(BodyType.self, from: data)
return response
} catch {
throw error
}
}
}
Usage:
if let data = str.data(using: .utf8) {
do {
let response = try Paginable<Segment>.decode(data: data, to: Paginable<Segment>.self)
print(response)
} catch {
print(error)
}
}
Edit:
JSON format:
{
"data": [
{
"name": "Name-1"
},
{
"name": "Name-2"
}
],
"meta": {
"pagination": {
"total": 100,
"count": 10,
"perPage": 5,
"currentPage": 1,
"totalPages": 10
}
}
}
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!
}