Не могу распарсить данные JSON

json

#1

Пытаюсь раскрасить данные, чтобы потом выложить в TableView, но ловлю ошибку:

keyNotFound(App.Animals.(CodingKeys in _EBC6E47D2E8ACFFEC4B52F4698C3D2D3).animal, Swift.DecodingError.Context(codingPath: [], debugDescription: “No value associated with key animal (“animal”).”, underlyingError: nil))

Вот мой код:

private var animals = [Animal]()

func getAnimal() {
        
        guard let url = URL(string: "http://test.com/rest/animal") else { return }
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.addValue("application/json", forHTTPHeaderField: "Accepts")
        
        let session = URLSession.shared
        session.dataTask(with: request) { (data, response, error) in
            if let response = response {
                print(response)
                
            }
            
            if let data = data {
                do {
                    guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any] else {return}
                    print("(\n\n\n\n\n\(json)")
                    
                    let decoder = JSONDecoder()
                    let downloadedAnimals = try decoder.decode(Animals.self, from: data)
                    self.animals = downloadedAnimals.animal
                    print("my animal = \(self.animals)")
                    
                    DispatchQueue.main.async {
                        self.tableView.reloadData()
                    }
                    
                } catch {
                    print(error)
                }
            }
        }.resume()
      }


class Animals: Codable {
    let animal: [Animal]

init(animal: [Animal]) {
    self.animal = animal
}

}

class Animal: Codable {
    let animalName: [String]
    let animalAbout: [String]

init(animal_title: [String], animal_about: [String]){
    self.animalName = animal_title
    self.animalAbout = animal_about
}

}

Сам JSON, что я пытаюсь распарсить выглядит таким образом:

 animal =     {
        "animal_about" = "[ANIMAL_ABOUT]";
        "animal_birthdate" = "03.06.2007";
        "animal_color" = Green;
        "animal_image" =         {
            alt = "";
            src = "http://test.com/sites/default/files/din1.jpg";
        };
        "animal_title" = "[ANIMAL_NAME]";
    };

#2

Если честно, то мне не нравиться ваша организация классов.
Сам JSON так же выглядит странно.

Что выводится тут
print("(\n\n\n\n\n\(json)")

P.S. еще мне кажется вы забыли указать codingKeys. Посмотрите тут пример https://gist.github.com/kobeumut/b06015646aa0d5f072bfe14e499690ef

P.S.S. а вообще, у вас не верно декодируется “data”, т.к. класс Animals имеет одно свойство animal: [Animal], а вы передаете туда такую структуру

{
        "animal_about" = "[ANIMAL_ABOUT]";
        "animal_birthdate" = "03.06.2007";
        "animal_color" = Green;
        "animal_image" =         {
            alt = "";
            src = "http://test.com/sites/default/files/din1.jpg";
        };
        "animal_title" = "[ANIMAL_NAME]";
    };

Decoder просто не видит за что зацепиться, он как минимум ожидает свойство “animal” в этой структуре JSON.
Вам нужна примерно такая структура

{
   "animal": [
      {"animal_name": ..., "animal_about: ...},
      {"animal_name": ..., "animal_about: ...},
      ...
   ]
}

#3

Хорошо, буду пробовать)

Ну а вообще я пытался сделать это по принципу с этого туториала:

Там где print(json) выводится скачаный json


#4

Если внимательно посмотрите, то там используется верный формат JSON’a + ключи в JSON’e совпадают с названиями свойств класса Actor. Поэтому он не создает CodingKeys.


#5

Насчет CodingKeys я вас понял.
А как мне тогда разобрать получаемый JSON в моем формате?


#6

Сперва покажите какой у вас JSON отправляется


#7

Вот в таком формате:

[«animals»: <__NSArrayM 0x604000450ad0>(
{
    animal =     {
        "animal_about" = "bulb ";
        "animal_birthdate" = "02.04.2018";
        "animal_color" = Red;
        "animal_image" =         {
            alt = "";
            src = "http://test.com/sites/default/files/IMG_20180322_175407_771.txt";
        };
        "animal_title" = hiking;
    };
},
{
    animal =     {
        "animal_about" = "People's best friend";
        "animal_birthdate" = "09.05.2005";
        "animal_color" = Purple;
        "animal_image" =         {
            alt = "";
            src = "http://test.com/sites/default/files/Free-shipping-Inflatable-Costume-Fan-Operated-Adult-Kids-Size-Halloween-Cosplay-Animal-Rider-T_6.jpg";
        };
        "animal_title" = "Random animal";
    };
},

#8

Было бы лучше увидеть JSON ответ из браузера. Можете в браузере открыть свою ссылку и там должен быть JSON.

Если JSON будет такой:

{
   "animals": [
      {"animal_name": ..., "animal_about: ...},
      {"animal_name": ..., "animal_about: ...},
      ...
   ]
}

То вам думаю достаточно будет в классе Animals, сделать свойство “animals” вместо “animal”

P.S. в ошибке кстати так и говорится CodingKeys in _EBC6E47D2E8ACFFEC4B52F4698C3D2D3).animal, CodingKeys не видит такого ключа. По дефолту CodingKeys ищет ключи совпадающие с именем свойств. Для класса Animal вам 100% придется делать CodingKeys, т.к. ключей в JSON’e как “animalName”, “animalAbout” нету.


#9

Вот ответ из браузера:

{«animals":
	[{"animal":
  		{"animal_title":"hiking",
		"animal_color":"Red",
		"animal_birthdate":"02.04.2018",
		"animal_image":{"src":"http:\/\/test.com\/sites\/default\/files\/IMG_20180322_175407_771.txt","alt":""},
		"animal_about":"bulb "}},
 	{"animal":
		{"animal_title":"Random animal",
		"animal_color":"Purple",
		"animal_birthdate":"09.05.2005",
		"animal_image":{"src":"http:\/\/test.com\/sites\/default\/files\/Free-shipping-Inflatable-Costume-Fan-Operated-Adult-                         
                  Kids-Size-Halloween-Cosplay-Animal-Rider-T_6.jpg","alt":""},
		"animal_about":"People's best friend"}},
	{"animal":
		{"animal_title":"Abstract Dino",
		"animal_color":"Red",
		"animal_birthdate":"15.04.2010",
		"animal_image":{"src":"http:\/\/test.com\/sites\/default\/files\/animal_1024x1024_3.png","alt":""},
		"animal_about":"Not specified Animal»}}

#10

Я бы рекомендовал убрать ключ “animal”, что бы JSON был такого вида

{"animals": [
{"animal_title":"hiking","animal_color":"Red","animal_birthdate":"02.04.2018","animal_image":{"src":"http:\/\/test.com\/sites\/default\/files\/IMG_20180322_175407_771.txt","alt":""},"animal_about":"bulb "},

{"animal_title":"Random animal","animal_color":"Purple","animal_birthdate":"09.05.2005","animal_image":{"src":"http:\/\/test.com\/sites\/default\/files\/Free-shipping-Inflatable-Costume-Fan-Operated-Adult-Kids-Size-Halloween-Cosplay-Animal-Rider-T_6.jpg","alt":""},"animal_about":"People's best friend"},

{"animal_title":"Abstract Dino","animal_color":"Red","animal_birthdate":"15.04.2010","animal_image":{"src":"http:\/\/test.com\/sites\/default\/files\/animal_1024x1024_3.png","alt":""},"animal_about":"Not specified Animal"}
]}

#11

К сожалению не могу этого сделать. Я никак не могу влиять на сервер(


#12

Я пока вижу такое решение: создать еще один класс, промежуточный, который будет хранить это лишнее свойство “animal”.
Получится примерно так:

class Animals: Codable {
   let animals: [Animal]
   ...
}

class Animal: Codable {
   let animal: AnimalData
   ...
}

class AnimalData: Codable {
   let animalName: String
   let animalAbout: String
   ...
   enum CodingKeys: String, CodingKey {
        case animalName = "animal_name"
        case animalAbout = "animal_about"
        ...
    }
}

#13

Переделал, но мне выдало такую ошибку:

typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [App.Animals.(CodingKeys in _EBC6E47D2E8ACFFEC4B52F4698C3D2D3).animals, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: “Index 0”, intValue: Optional(0)), App.Animal.(CodingKeys in _EBC6E47D2E8ACFFEC4B52F4698C3D2D3).animal, App.AnimalData.(CodingKeys in _EBC6E47D2E8ACFFEC4B52F4698C3D2D3).animalName], debugDescription: “Expected to decode Array but found a string/data instead.”, underlyingError: nil))

Я поменял получаемые значения, записываемые в Animal data c [String] на String

init(animal_title: String, animal_about: String){
    self.animalName = animal_title
    self.animalAbout = animal_about
}
private enum CodingKeys: String, CodingKey {
    case animalName = "animal_title"
    case animalAbout = "animal_about"
}

Вывело такое :

my animal = [App.Animal, App.Animal, App.Animal, App.Animal, App.Animal, App.Animal, App.Animal…]

И упало.


#14

В классе AnimalData нужно сделать
let animalName: String

А у вас все еще
let animalName: [String]
Судя по ошибке


#15

Если я меняю [String] на String в AnimalData, то у меня падает и пишет следующее:

my animal = [App.Animal, App.Animal, App.Animal, App.Animal, App.Animal, App.Animal, App.Animal…]

Код:

class Animals: Codable {
    let animals: [Animal]
    
    init(animals: [Animal]) {
        self.animals = animals
    }
    
}

class Animal: Codable {
    let animal: AnimalData
    
}

class AnimalData: Codable {
    let animalName: String // и тут 
    let animalAbout: String

    init(animal_title: String, animal_about: String){ // меняю  тут 
        self.animalName = animal_title
        self.animalAbout = animal_about
    }
    
    private enum CodingKeys: String, CodingKey {
        case animalName = "animal_title"
        case animalAbout = "animal_about"
    }
}

#16

С классами тут все правильно.
Покажите скрин контроллера теперь.


#17

Запускается, но выдает следующую ошибку:

Вот:

import UIKit

class AllAnimals: UIViewController{

var animals = [Animal]()
@IBOutlet weak var tableView: UITableView!

override func viewDidLoad() {
    getAnimals()
}
func getAnimals() {
    
    guard let url = URL(string: "http://test.com/rest/animals") else { return }
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accepts")
    
    let session = URLSession.shared
    session.dataTask(with: request) { (data, response, error) in
        if let response = response {
            print(response)
            
        }
        
        if let data = data {
            do {
                guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any] else {return}
                print("(\n\n\n\n\n\(json)")
                
                let decoder = JSONDecoder()
                let downloadedDinos = try decoder.decode(Dinos.self, from: data)
                self.animals = downloadedDinos.animals
                print("my dino = \(self.animals)")
                
                DispatchQueue.main.async {
                    self.tableView.reloadData() 
                    //Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
                }
                
            } catch {
                print(error)
            }
        }
        }.resume()
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return animals.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? CustomCell else { return UITableViewCell() }
    
    cell.name.text = "Name: " + animals[indexPath.row].animal.animalName
    cell.about.text = "About: " + animals[indexPath.row].animal.animalAbout
            return cell
}

}

  • выводиться:

my animal = [App.Animal, App.Animal, App.Animal, App.Animal, App.Animal, App.Animal…]


#18

Уже можно не писать CodingKeys для snake_case https://developer.apple.com/documentation/foundation/jsondecoder/keydecodingstrategy/convertfromsnakecase

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

#19

Вроде заработало)

Оставляю пример рабочего кода ( вдруг кому пригодиться ):

import UIKit

class AllAnimal: UIViewController, UITableViewDataSource{

    var animals = [Animal]()
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        getAnimals()
    }
        func getAnimals() {
            
            guard let url = URL(string: "http://test.com/rest/dinos") else { return }
            var request = URLRequest(url: url)
            request.httpMethod = "GET"
            request.addValue("application/json", forHTTPHeaderField: "Content-Type")
            request.addValue("application/json", forHTTPHeaderField: "Accepts")
            
            let session = URLSession.shared
            session.dataTask(with: request) { (data, response, error) in
                if let response = response {
                    print(response)
                    
                }
                
                if let data = data {
                    do {
                        guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any] else {return}
                        print("(\n\n\n\n\n\(json)")
                        
                        let decoder = JSONDecoder()
                        let downloadedDinos = try decoder.decode(Dinos.self, from: data)
                        self.dinos = downloadedDinos.dinos
                        print("my dino = \(self.dinos)")
                        
                        DispatchQueue.main.async {
                            self.tableView.reloadData()
                        }
                        
                    } catch {
                        print(error)
                    }
                }
            }.resume()
          }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return animals.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? CustomCell else { return UITableViewCell() }
        
        cell.name.text = "Name: " + animals[indexPath.row].animal.animalName
        
        cell.about.text = "About: " + animals[indexPath.row].animal.animalAbout

            
        
        return cell
    }
            
    
}




import UIKit



class Animals: Codable {
    let animals: [Animal]
    
    init(animals: [Animal]) {
        self.animals = animals
    }
    
    private enum CodingKeys: String, CodingKey {
        case animals = «animals»
    }
}

class Animal: Codable {
    let animal: AnimalData
    
   
}

class AnimalData: Codable {
    let animalName: String
    let animalAbout: String

    init(animal_title: String, animal_about: String){
        self.animalName = animal_title
        self.animalAbout = animal_about
    }
    
    private enum CodingKeys: String, CodingKey {
        case animalName = «animal_title"
        case animalAbout = «animal_about"
    }
}

#20

Вам огромнейшее спасибо за помощь!:smiley::smiley::smiley: