Error in parsing JSON using codeable [duplicate] - json

This question already has answers here:
Can comments be used in JSON?
(58 answers)
Closed 4 years ago.
I am trying to pass some data from a JSON file using the new(ish) codeable capability in Swift. I have used the below syntax before without issue. I believe I may have something set up wrong, however, as I can't seem to understand why I keep receiving the below message when the JSON format has been approved by a JSON parser.
The error message:
error:dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.})))
The code in my QuestionFactory file...
class QuestionFactory {
func parseJSON(filename fileName: String) -> Quiz? {
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
print(url)
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
print("data received is \(data.count) bytes:\n\(data)")
print(data)
print(data as NSData)
let jsonData = try decoder.decode(Quiz.self, from: data)
print(jsonData)
} catch {
print("error:\(error)")
}
}
return nil
}
}
The code in my initial ViewController:
class LaunchScreen: UIViewController {
private var quiz: Quiz?
private let jsonFileName = "QuizData"
func viewDidLoad() {
super.viewDidLoad()
createQuiz()
}
private func createQuiz() {
let questionFactory = QuestionFactory()
guard let parsedQuiz = questionFactory.parseJSON(filename: jsonFileName) else {
print("Error creating quiz")
return
}
quiz = parsedQuiz
}
func movesToMainMenuScreen() {
let transition = CATransition()
transition.duration = 1.5
transition.type = kCATransitionFade
self.navigationController?.view.layer.add(transition, forKey:nil)
let mainMenuVC: UIViewController = MainMenuViewController(quiz: quiz!) >> I am receiving an error here as well, perhaps due to my mainMenuVC's required init?
navigationController?.pushViewController(mainMenuVC, animated: false)
}
In my mainMenuViewController:
class mainMenuViewController: UIViewController {
private var quiz: Quiz! {
didSet {
tableViewAdapter = AnswerTableViewAdapter(answers: quiz.questions[0].answers) >> Although, it is not obviously reaching this far to read through the JSON.
}
required init(quiz: Quiz) {
super.init(nibName: nil, bundle: nil)
defer {
self.quiz = quiz
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The JSON looks like this...
{
"questions":[
{
"text": "1. Where will the 2022 World cup be held?",
"answers": [
{
"text": "Qatar",
"isCorrect": true,
"answerType": "2"
},
{
"text": "دولة قطر",
"isCorrect": true,
"answerType": "1"
},
{
"text": "Jamaica",
"isCorrect": false,
"answerType": "0"
},
{
"image":"qatarFlag",
"isCorrect": true,
"answerType": "3"
}
]
}]
}
The Model files....
Quiz.swift
import Foundation
struct Quiz: Decodable {
var questions: [Question]
}
Question.swift
import Foundation
struct Question: Decodable {
var text: String
var answers: [Answer]
}
Answer.swift
import Foundation
struct Answer: Decodable {
var text: String
var image: String
var isCorrect: Bool
var answerType: String
}

There is extra
]
}
added on last two lines remove these both closing brackets and try to parse JSON.
And make your model's properties to optional to avoid nullable crashes.
The Model files....
Quiz.swift
import Foundation
struct Quiz: Decodable {
var questions: [Question]?
}
Question.swift
import Foundation
struct Question: Decodable {
var text: String?
var answers: [Answer]?
}
Answer.swift
import Foundation
struct Answer: Decodable {
var text: String?
var image: String?
var isCorrect: Bool?
var answerType: String?
}

Related

Importing JSON from URL within a view

So, new to swift and was able to peice this together by reading and watching videos however i reached a point after countless hours searching for a solution..
Basically what the app does is scan's a qr code, parses the url it reads from the qr code to get a key, then I am appending that key to the api url, and i want to output the results from the api to the screen. however I am receiving an error Type '()' cannot conform to 'View' in xcode
Here is sample json data
[
{
"id": "160468",
"sport": "BASKETBALL",
"year": "2020",
"brand": "PANINI PRIZM",
"cardNumber": "278",
"playerName": "LaMELO BALL",
"extra": "",
"gradeName": "MINT",
"grade": "9",
"serial": "63585906",
"authDate": "1656406800",
"link": "https://www.example.com/certificate-verification?certificateNumber=63585906"
}
]
here is my contentview
import SwiftUI
import CodeScanner
extension URL {
var components: URLComponents? {
return URLComponents(url: self, resolvingAgainstBaseURL: false)
}
}
extension Array where Iterator.Element == URLQueryItem {
subscript(_ key: String) -> String? {
return first(where: { $0.name == key })?.value
}
}
struct Card: Decodable {
let sport: String
let year: String
let brand: String
let cardNumber: String
let playerName: String
let extra: String
let gradeName: String
let grade: String
let serial: String
}
struct ContentView: View {
#State var isPresentingScanner = false
#State var scannedCode: String = ""
var scannerSheet : some View {
CodeScannerView(
codeTypes: [.qr],
completion: { result in
if case let .success(code) = result {
self.scannedCode = code.string
self.isPresentingScanner = false
}
}
)
}
func getQueryStringParameter(url: String, param: String) -> String? {
guard let url = URLComponents(string: url) else { return nil }
return url.queryItems?.first(where: { $0.name == param })?.value
}
var body: some View {
VStack(spacing: 10) {
Image("logo-white")
.offset(y: -200)
if let urlComponents = URL(string: scannedCode)?.components,
let cert = urlComponents.queryItems?["certificateNumber"] {
//Text(cert)
let apihit = URL(string: "https://app.example.com/api.php?apikey=xxx&cert=\(cert)")!
//Text(apihit.absoluteString)
var request = URLRequest(url: apihit)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: apihit) { data, response, error in
if let data = data {
if let cards = try? JSONDecoder().decode([Card].self, from: data) {
print(cards)
} else {
print("Invalid Response")
}
} else if let error = error {
print("HTTP Request Failed \(error)")
}
}
}
Button("Scan QR Code") {
self.isPresentingScanner = true
}
.padding()
.background(Color(red: 0, green: 0, blue: 0.5))
.foregroundColor(.white)
.clipShape(Rectangle())
.cornerRadius(20)
.sheet(isPresented: $isPresentingScanner) {
self.scannerSheet
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.gray)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I have tried countless tutorials online, however none of them show how to do it within the view, which I believe is where it belongs because I don't get the actual json url until after I read the qr code..
To populate a link within a view, use Webkit.
First you need to import Webkit. Then, create a WebView which conforms to the UIViewRepresentable protocol.
Here is the Apple documentation. This is the easiest way to integrate web content into your app.
I hope this helps!

Json not loading in SwiftUI

am trying to load this json into my swiftui app
{
"id": "EF1CC5BB-4785-4M8E-AB98-5FA4E00B6A66",
"name" : "opening the box",
"group" : [
{
"name" : "Open box",
"windows" : "Double-click",
"mac" : "right-click"
},
{
"name" : "Duplicate",
"windows" : "right click and choose from options",
"mac" : "option + drag"
}
]
},
and there are many more elements like this
and here are the structs that I made
struct topic: Codable, Identifiable {
var id: UUID
var name: String
var group: [shortcuts]
}
struct shortcuts: Codable, Equatable, Identifiable{
var id = UUID()
var name: String
var windows: String
var mac: String
}
and here is the code for decoding it
import UIKit
extension Bundle {
func decode<T: Decodable>(_ type: T.Type, from file: String) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("failed")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("failed to do it")
}
let decoder = JSONDecoder()
guard let loaded = try? decoder.decode(T.self, from: data) else {
fatalError("failed")
}
return loaded
}
}
now when I create a constant like this inside my view
let Topic = Bundle.main.decode([topic].self, from: "main.json")
and then I try to load the items like this
NavigationView{
List{
ForEach(Topic) { section in
Section(header: Text(section.name)) {
ForEach(section.group) { item in
shortcutRow(item: item)
}
}
}
}.navigationBarTitle("Menu")
.listStyle(SidebarListStyle())
}
it doesn't work it shows fatal error that is present in the second last line of code of my bundle extension that I created for decoding this json
please help
"...and there are many more elements like this", that means the json you show
is probably not the json you have. Most likely it is an array of what you show.
Thus, in Bundle decode, you should decode and return [T] not T.
Note you should use the normal convention of using uppercased name for types,
such as Topic, and lowercased name for instances, topic. Given that, try this:
extension Bundle {
// todo deal with errors
func decode<T: Decodable>(from file: String) -> [T] { // <-- here
if let url = Bundle.main.url(forResource: file, withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let loaded = try JSONDecoder().decode([T].self, from: data) // <-- here
return loaded
} catch {
// todo deal with errors
print("---> error: \(error)")
}
} else {
// todo deal with errors
print("---> error url: ")
}
return []
}
}
struct Topic: Codable, Identifiable {
var id, name: String
var group: [Shortcuts]
}
struct Shortcuts: Codable, Identifiable {
let id = UUID() // <-- here use let
var name, windows, mac: String
}
struct ContentView: View {
let topics: [Topic] = Bundle.main.decode(from: "main") // <-- here
var body: some View {
Text("testing topics: \(topics.count)")
.onAppear {
print("---> topics: \(topics)")
}
}
}
EDIT:
This is the json data I used to test my answer:
[
{
"id": "EF1CC5BB-4785-4M8E-AB98-5FA4E00B6A66",
"name" : "opening the box",
"group" : [
{
"name" : "Open box",
"windows" : "Double-click",
"mac" : "right-click"
},
{
"name" : "Duplicate",
"windows" : "right click and choose from options",
"mac" : "option + drag"
}
]
}
]

TypeMismatch error in filling List from JSON

i am trying to fill a list from JSON in SwiftUI, which has this format:
{
"books": [{
"id": "87",
"title": "2001 odissea nello spazio",
"author_id": null,
"author": "arthur c. clarke",
"editor_id": null,
"editor": "longanesi",
"price": "0.00",
"isbn": "",
"note": ""
}, ......]
}
i created this struct for the Book object:
struct Book: Decodable, Identifiable {
public var id: Int;
public var title: String;
public var isbn: String;
enum CodingKeys: String, CodingKey {
case id = "id";
case title = "title";
case isbn = "isbn";
}
}
then i created this class to get the remote json:
import Foundation
public class GetBooks: ObservableObject {
#Published var books = [Book]();
init() {
load();
}
func load() {
let url = URL(string: "https://www.mattepuffo.com/api/book/get.php")!;
URLSession.shared.dataTask(with: url) {
(data, response, error) in
do {
if let d = data {
let decodedLists = JSONDecoder();
decodedLists.keyDecodingStrategy = .convertFromSnakeCase;
let dec = try decodedLists.decode([Book].self, from: d);
DispatchQueue.main.async {
self.books = dec;
}
} else {
print("Non ci sono libri");
}
} catch {
print(error)
}
}.resume();
}
}
but i get an error: typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
I think I understand what the problem is, but I don't understand how to solve it.
in the sense that the problem is that the json starts with an object (books) and not with an array.
but I don't understand how I have to modify the code!
I also tried to modify this line in this way, getting the error you see in the comment:
let dec = try decodedLists.decode(Book.self, from: d);
DispatchQueue.main.async {
self.books = dec; // Cannot assign value of type 'Book' to type '[Book]'
}
Your problem is that your JSON is not an Array of Book.
You need an upper level struct:
struct BookList: Decodable {
let books : [Book]
}
and then decode this structure instead of the array:
let dec = try decodedLists.decode(BookList.self, from: d);
DispatchQueue.main.async {
self.books = dec.books;
}
There are two major issues in your code:
You are ignoring the root object of the JSON, the dictionary with key books. This causes the error.
The type of key id is a string, in JSON everything in double quotes is String.
Further you don't need CodingKeys if all struct member names match the JSON keys and if the struct members are not going to be modified declare them as constants (let). Finally this is Swift: No trailing objective-c-ish semicolons.
struct Root: Decodable {
public let books: [Book]
}
struct Book: Decodable, Identifiable {
public let id: String
public let title: String
public let isbn: String
}
let result = try decodedLists.decode(Root.self, from: d)
DispatchQueue.main.async {
self.books = result.books
}

I'm trying print data from my response but there is an error

I can print only 1 data, not any more. This is my error:
Thread 1: Fatal error: Index out of range
This is my JSON:
[
{
"Guides": [
{
"_id": "5cbc780edfdb6307006aec37",
"Text": "He is one of Soroush Friend",
"Tavernier": 2
},
{
"_id": "5cbc781bdfdb6307006aec38",
"Text": "He is one of Soroush Friend",
"Tavernier": 2
}
]
}
]
And this is my struct that works well:
struct GuideStruct: Codable {
let guides: [Guide]
enum CodingKeys: String, CodingKey {
case guides = "Guides"
}
}
struct Guide: Codable {
let id, text: String
let tavernier: Int
enum CodingKeys: String, CodingKey {
case id = "_id"
case text = "Text"
case tavernier = "Tavernier"
}
}
And this is my array and my class:
internal static var guides = [guidesarr]()
class guidesarr {
var _id : String
var Text : String
var Tavernier : Int
init(_id : String,Text : String,Tavernier : Int) {
self._id = _id
self.Text = Text
self.Tavernier = Tavernier
}
}
And my codes in viewcontroller:
class GameViewController: UIViewController,UITextFieldDelegate {
typealias guide1 = [GuideStruct]
var i1 = 0
override func viewDidLoad() {
super.viewDidLoad()
let headers : HTTPHeaders = ["Content-Type":"application/json","OAtcp":"0!QSJ5SDG8Q39PPM$DXP5HD1E10"]
Alamofire.request("http://192.168.1.100:3535/DarkDiamonds/Api/GetActiveGames",method :.post,headers: headers).responseJSON { (newresponse) in
do {
let decoder = JSONDecoder()
let responseguide = try decoder.decode(guide1.self, from: newresponse.data!)
for each1 in responseguide {
let newstruct = guidesarr(_id:each1.guides[self.i1].id , Text: each1.guides[self.i1].text, Tavernier: each1.guides[self.i1].tavernier)
self.i1 = self.i1 + 1
AppDelegate.guides.append(newstruct)
}
print(AppDelegate.guides[0])
print(AppDelegate.guides[1])
print(AppDelegate.Games.count)
print(AppDelegate.guides[0].Text)
print(AppDelegate.guides[1].Text)
}catch {
}
}
}
}
I can print:
print(AppDelegate.guides[0])
And print this:
print(AppDelegate.guides[0].Text)
But when I want to print this:
print(AppDelegate.guides[1])
print(AppDelegate.guides[1].Text)
There is error:
Thread 1: Fatal error: Index out of range
There are several issues in your code.
The guidesarr class is unnecessary. Just use your Guide struct.
Use proper naming conventions. Class, struct, and enum names should start with uppercase letters. Property, function, and case names should start with lowercase letters.
Don't force-unwrap data. Safely check it and do proper error checking.
Your main issue is that the two chunks of data you seem to actually want are the two Guide instances inside the one (not two) GuideStruct instances.
I would redo your code something like this:
class GameViewController: UIViewController,UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let headers : HTTPHeaders = ["Content-Type":"application/json","OAtcp":"0!QSJ5SDG8Q39PPM$DXP5HD1E10"]
Alamofire.request("http://192.168.1.100:3535/DarkDiamonds/Api/GetActiveGames", method: .post, headers: headers).responseJSON { (newresponse) in
if let data = newresponse.data {
do {
let decoder = JSONDecoder()
let guides = try decoder.decode([GuideStruct].self, from: data)
for guideStruct in guides {
AppDelegate.guides.append(contentsOf: guideStruct.guides)
}
print(AppDelegate.guides[0])
print(AppDelegate.guides[1])
print(AppDelegate.Games.count)
print(AppDelegate.guides[0].Text)
print(AppDelegate.guides[1].Text)
}catch {
// Bad JSON
}
} else {
// No data
}
}
}
}
And change:
internal static var guides = [guidesarr]()
to:
internal static var guides = [Guide]()
And delete the guidearr class.

How to parse JSON with custom parameters using Codable protocol

I have a JSON with keys
{
"yearOfManufacture":"20/9/2018",
"carSize":8,
"isNew":true,
"carAssets":[
{
"color":"5761807993001",
"nativeId":"{\"app\":\"1234/Car/Native_App\",\"web\":\" /8888/Car/Native_Car_Desktop\"}"
}
]
}
I am trying to parse using Codable protocol with struct models
struct Cars: Codable {
var yearOfManufacture: String?
var carSize: Int = 0
var isNew: Bool = true
var carAssets: [CarAssests]?
}
struct CarAssests: Codable {
var color: String?
var nativeId: String?
}
I am getting error like The data couldn’t be read because it isn’t in the correct format. I tried using CodingKeys with decoder container not getting the exact type of "nativeId": "{\"app\":\"1234/Car/Native_App\",\"web\":\" /8888/Car/Native_Car_Desktop\"}" not getting exact data type of this.
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .deferredToData
if let jsonData = jsonString.data(using: .utf8) {
do {
print(jsonData)
let assets = try decoder.decode(Cars.self, from: jsonData)
print(assets)
} catch {
print(error.localizedDescription)
}
}
I bet you are doing something like this:
let jsonString = """
{
"yearOfManufacture": "20/9/2018",
"carSize": 8,
"isNew": true,
"carAssets": [
{
"color": "5761807993001",
"nativeId": "{\"app\":\"1234/Car/Native_App\",\"web\":\" /8888/Car/Native_Car_Desktop\"}"
}
]
}
"""
In a multiline string, both \" and " mean the character ". So you have to write \\" to get the two characters \ and ":
let jsonString = """
{
"yearOfManufacture": "20/9/2018",
"carSize": 8,
"isNew": true,
"carAssets": [
{
"color": "5761807993001",
"nativeId": "{\\"app\\":\\"1234/Car/Native_App\\",\\"web\\":\\" /8888/Car/Native_Car_Desktop\\"}"
}
]
}
"""