I have an issue parsing following JSON, the problem is that it starts with an array. What should be the initial struct where I would identify the array? if there was something like "data" before the array I would create another struct and mention data: [Item]? there but this JSON just starts with array.
[
{
"userId": 1,
"id": 1,
"title": "TEST text"
},
{
"userId": 2,
"id": 2,
"title": "TEST text"
},
{
"userId": 3,
"id": 3,
"title": "TEST text"
}
]
struct Item: Codable {
var userId: Int?
var id: Int?
var title: String?
}
You have to add one more struct:
First Way
struct totalItem: Codable {
var total: [Item]?
}
struct Item: Codable {
var userId: Int?
var id: Int?
var title: String?
}
let myStruct = try JSONDecoder().decode(totalItem.self, from: data)
The second way to do that:
let myStruct = try JSONDecoder().decode([Item].self, from: data )
Related
I have been trying to decode this Json data but I'm not able to do it completly :
This is my sample json data :
{
"id": 10644,
"name": "CP2500",
"numberOfConnectors": 2,
"connectors": [
{
"id": 59985,
"name": "CP2500 - 1",
"maxchspeed": 22.08,
"connector": 1,
"description": "AVAILABLE"
},
{
"id": 59986,
"name": "CP2500 - 2",
"maxchspeed": 22.08,
"connector": 2,
"description": "AVAILABLE"
}
]
}
this is my struct :
`
struct Root: Codable {
var id: Int
var name: String
var numberOfConnectors: Int
var connectors: [Connector]
}
struct Connector: Codable {
var id: Int
var name: String
var maxchspeed: Double
var connector: Int
var connectorDescription: String
enum CodingKeys: String, CodingKey {
case id, name, maxchspeed, connector
case connectorDescription = "description"
}
}
I want to parse the element within the [Connector] array but I'm just getting the elements of the Root level :
let jsonData = array.data(using: .utf8)!
let root = try JSONDecoder().decode(Root.self, from: jsonData)
print("\(root.id)")
Any idea how to do this ?
do {
let root = try JSONDecoder().decode(Root.self, from: jsonData)
print("root id : \(root.id)")
root.connectors.forEach {
print("name : \($0.name),"," connector id : \($0.id),","status : \($0.description)");
}
} catch {
print(error.localizedDescription)
}
I'm new to SwiftUI and trying to build a simple single-player Quiz app. I started by following this tutorial and then tried to continue on my own. Currently, the questions are hardcoded in my ViewModel but I'd like to change it to fetching from a local JSON file instead. Anyone that can point me in the right direction?
Here's a part of my ViewModel with the questions as static data:
extension GameManagerVM {
static var questions = quizData.shuffled()
static var quizData: [QuizModel] {
[
QuizModel(
id: 1,
question: "Question title 1",
category: "Sport",
answer: "A",
options: [
QuizOption(id: 11, optionId: "A", option: "A"),
QuizOption(id: 12, optionId: "B", option: "B"),
QuizOption(id: 13, optionId: "C", option: "C"),
QuizOption(id: 14, optionId: "D", option: "D")
]
),
...
}
}
Here's a test I did, but I get the error that Xcode can't decode it.
I replace the above code with this:
extension GameManagerVM {
static var questions = quizData.shuffled()
static var quizData: [QuizModel] = Bundle.main.decode("quizquestions2022.json")
}
And here's the JSON.
[
{
"id": "1",
"question": "Question title 1",
"category": "Sport",
"answer": "A",
"options": [
{
"id": "1001",
"optionId": "A",
"option": "A"
},
{
"id": "1002",
"optionId": "B",
"option": "B"
},
{
"id": "1003",
"optionId": "C",
"option": "C"
},
{
"id": "1004",
"optionId": "D",
"option": "D"
}
]
},
]
Here are my models
struct Quiz {
var currentQuestionIndex: Int
var quizModel: QuizModel
var quizCompleted: Bool = false
var quizWinningStatus: Bool = false
var score: Int = 0
}
struct QuizModel: Identifiable, Codable {
var id: Int
var question: String
var category: String
var answer: String
var options: [QuizOption]
}
struct QuizOption: Identifiable, Codable {
var id: Int
var optionId: String
var option: String
var isSelected: Bool = false
var isMatched: Bool = false
}
When you are decoding, unless you make your own decoding like this sample init from decoder Then all of non-optional the vars or lets in the struct need to be in the json. Your data doesn't have isSelected or isMatched in the options, so those need to be optional
struct QuizOption: Identifiable, Codable {
var id: Int
var optionId: String
var option: String
var isSelected: Bool?
var isMatched: Bool?
to fetch your data from a local JSON file, you could try
this approach, where you need to have a model (QuizModel) to
match the json data in your file. Also I used a class GameManagerVM: ObservableObject
to hold/publish your data as an example:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#StateObject var gameManager = GameManagerVM()
var body: some View {
List {
ForEach(gameManager.quizData) { quiz in
Text(quiz.question)
}
}
}
}
struct QuizModel: Identifiable, Codable {
let id, question, category, answer: String
let options: [QuizOption]
enum CodingKeys: String, CodingKey {
case id, question, category, answer, options
}
}
struct QuizOption: Codable {
let id, optionId, option: String
var isSelected: Bool = false
var isMatched: Bool = false
enum CodingKeys: String, CodingKey {
case id, optionId, option
}
}
class GameManagerVM: ObservableObject {
#Published var questions: [QuizModel] = []
#Published var quizData: [QuizModel] = []
init() {
quizData = Bundle.main.decode("quizquestions2022.json")
questions = quizData.shuffled()
}
}
This question might seem similar to other questions with the same title. However, my decodable is in a function called load.
Here is what my JSON file looks like:
[
{
"id": 1001
"name": "tempName"
"item1": [
{
"id": 1101
"element": "tempElement"
},
{
"id": 1201
"element": "tempElement2"
},
]
}
]
I've built a struct temp to access the information within my JSON file after decoding it. I am not sure if it is extremely relevant but this is what it looks like:
struct Temp: Hashable, Codable, Identifiable {
var id: Int
var name: String
var item1: ExampleItem
}
struct ExampleItem: Hashable, Codable, Identifiable{
var id: Int
var element: String
}
My structs don't seem too crazy so I assume the problem occurs when I am parsing and decoding my JSON. This is the code I am using
let tempData: [Temp] = load("tempData.json")
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
I think Expected to decode Dictionary<String, Any> but found an array instead bug is occurring when the code tries to decode item1 because item1 is an array. What should I change in my load function?
First of all your JSON is invalid. It should be comma-separated:
[
{
"id": 1001,
"name": "tempName",
"item1": [
{
"id": 1101,
"element": "tempElement"
},
{
"id": 1201,
"element": "tempElement2"
},
]
}
]
Then you need to parse item1 as an array:
struct Temp: Hashable, Codable, Identifiable {
var id: Int
var name: String
var item1: [ExampleItem] // <- declare as an array
}
Note: I suggest renaming item1 to items. For this you need to use CodingKeys (it is as a custom mapping between a model and a JSON):
struct Temp: Hashable, Codable, Identifiable {
enum CodingKeys: String, CodingKey {
case id, name, items = "item1"
}
var id: Int
var name: String
var items: [ExampleItem]
}
Cannot read data from section "weather" of JSONDecoder file, got via Almofire
the data printed in console:
{"coord":{"lon":-0.13,"lat":51.51},"weather":[{"id":521,"main":"Rain","description":"shower
rain","icon":"09d"}],"base":"stations","main":{"temp":289.64,"pressure":1006,"humidity":48,"temp_min":286.48,"temp_max":292.59},"visibility":10000,"wind":{"speed":1},"clouds":{"all":85},"dt":1558190870,"sys":{"type":1,"id":1414,"message":0.009,"country":"GB","sunrise":1558152298,"sunset":1558208948},"id":2643743,"name":"London","cod":200}
struct MyWeatherData: Codable {
let coord : coord
let weather : weather
}
struct coord: Codable {
let lon: Double
let lat: Double
}
struct weather: Codable {
let array : [unknown] //here is my problem
let base : String
}
struct unknown : Codable {
let id : Int
let main: String
let description : String
let icon : String
}
let cityLink = "https://api.openweathermap.org/data/2.5/weather?q=London"
Alamofire.request(self.cityLink+"&APPID=\(self.myId)").responseJSON { (response) in
// print("Request: \(String(describing: response.request))") // original url request
// print("Response: \(String(describing: response.response))")
// print("Result: \(response.result)")
if let data = response.data, let utf8 = String(data: data, encoding: .utf8) {
print("Data is: \(utf8)")
do {
let myData = try JSONDecoder().decode(MyWeatherData.self, from: data)
// print("lat is: \(myData.coord.lat)") //ok, working
print("weather is: \(myData.weather.main)") //not working
} catch let myError {
print("error is: ", myError)
}
}
}
No, the problem is not here is my problem, the problem is in MyWeatherData.
Please read the JSON. It's very easy. The value for key weather is wrapped in [] so the object is an array.
And name all structs with uppercase letters to avoid confusion like let weather : weather
struct MyWeatherData : Decodable {
let coord : Coord
let weather : [Weather]
}
struct Coord : Decodable {
let lon: Double
let lat: Double
}
struct Weather : Decodable {
let id : Int
let main: String
let description : String
let icon : String
}
Please name your class/Models with first letter Capitalized.
The problem is that weather is an Array on MyWeatherData so it becomes:
struct MyWeatherData: Codable {
let coord : Coord
let weather : [Weather]
}
struct Coord: Codable {
let lon: Double
let lat: Double
}
struct Weather: Codable {
let id : Int
let main: String
let description : String
let icon : String
}
In MyWeatherData the weather property should have [weather] type, as the JSON returns an array in weather key:
{
"coord": {
"lon": -0.13,
"lat": 51.51
},
"weather": [{
"id": 521,
"main": "Rain",
"description": "shower rain",
"icon": "09d"
}],
"base": "stations",
"main": {
"temp": 289.64,
"pressure": 1006,
"humidity": 48,
"temp_min": 286.48,
"temp_max": 292.59
},
"visibility": 10000,
"wind": {
"speed": 1
},
"clouds": {
"all": 85
},
"dt": 1558190870,
"sys": {
"type": 1,
"id": 1414,
"message": 0.009,
"country": "GB",
"sunrise": 1558152298,
"sunset": 1558208948
},
"id": 2643743,
"name": "London",
"cod": 200
}
So your types should look like this:
struct MyWeatherData: Codable {
let coord: coord
let weather: [weather]
let base: String
}
struct coord: Codable {
let lon: Double
let lat: Double
}
struct weather : Codable {
let id : Int
let main: String
let description : String
let icon : String
}
And then you can get weather instance by myWeatherData.weather.first
So I'm trying to parse JSON that looks something like this using Codable in Swift.
{
"abilities": [
{
"ability": {
"name": "chlorophyll",
"url": "https://pokeapi.co/api/v2/ability/34/"
},
"is_hidden": true,
"slot": 3
},
{
"ability": {
"name": "overgrow",
"url": "https://pokeapi.co/api/v2/ability/65/"
},
"is_hidden": false,
"slot": 1
}
],
"name": "SomeRandomName"
}
Now it gets confusing when you're trying to get nested data. Now I'm trying to get the name, which is easy. I'm also trying to get the ability name, this is where its gets complicated for me. After some research this is what I came up with.
class Pokemon: Codable {
struct Ability: Codable {
var isHidden: Bool
struct AbilityObject: Codable {
var name: String
var url: String
}
var ability: AbilityObject
private enum CodingKeys: String, CodingKey {
case isHidden = "is_hidden"
case ability
}
}
var name: String
var abilities: [Ability]
}
Now is there any better way in doing this, or am I stuck doing it like this.
Grab your JSON response and dump it in this site.
It'll generate these structs without Codable. Add Codable so they look like this:
struct Pokemon: Codable {
let abilities: [AbilityElement]
let name: String
struct AbilityElement: Codable {
let ability: Ability
let isHidden: Bool
let slot: Int
struct Ability: Codable {
let name: String
let url: String
}
}
}
For keys with snake_case, you can just declare a JSONDecoder and specify the keyDecodingStrategy as .convertFromSnakeCase. No need to muck around with coding keys if you're just converting from snake case. You only need them if you're renaming keys.
If you have other situations where you need to create custom coding keys for your responses or alter key names, this page should prove helpful.
You can dump this in a playground and play around with it:
let jsonResponse = """
{
"abilities": [
{
"ability": {
"name": "chlorophyll",
"url": "https://pokeapi.co/api/v2/ability/34/"
},
"is_hidden": true,
"slot": 3
},
{
"ability": {
"name": "overgrow",
"url": "https://pokeapi.co/api/v2/ability/65/"
},
"is_hidden": false,
"slot": 1
}
],
"name": "SomeRandomName"
}
"""
struct Pokemon: Codable {
let abilities: [AbilityElement]
let name: String
struct AbilityElement: Codable {
let ability: Ability
let isHidden: Bool
let slot: Int
struct Ability: Codable {
let name: String
let url: String
}
}
}
var pokemon: Pokemon?
do {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
if let data = jsonResponse.data(using: .utf8) {
pokemon = try jsonDecoder.decode(Pokemon.self, from: data)
}
} catch {
print("Something went horribly wrong:", error.localizedDescription)
}
print(pokemon)