Я знаю, что это второй раз, когда я спрашиваю о JSON, но я просто не могу понять, как это работает. Я продолжаю получать ошибки, потому что он не может загрузить данные... JSON, который я использую, немного сложнее, чем предыдущий.
{
"profile_v2": {
"name": {
"full_name": "test test",
"first_name": "test",
"middle_name": "",
"last_name": "test"
},
"emails": {
"emails": [
"[email protected]"
],
"previous_emails": [
"[email protected]"
],
"pending_emails": [
],
"ad_account_emails": [
]
},
"birthday": {
"year": 33,
"month": 33,
"day": 24
},
"gender": {
"gender_option": "MALE",
"pronoun": "MALE"
},
"previous_names": [
],
"other_names": [
],
"current_city": {
"name": "test",
"timestamp": 0
},
"hometown": {
"name": "test",
"timestamp": 0
},
"relationship": {
"status": "",
"timestamp": 1574556291
},
"education_experiences": [
],
"work_experiences": [
{
"employer": "test test test test",
"title": "test",
"location": "test test test",
"description": "",
"start_timestamp": 1246442400,
"timestamp": 1589226310
}
]
}
}
Questo invece è il mio codice per decodificare i dati
private let JSONFriendFileName = "Profile"
extension Bundle {
func decode<T: Decodable>(file: String) -> T {
guard let url = self.url(forResource: JSONFriendFileName, withExtension: "json") else {
fatalError("Non è stato possibile trovare il file \(file) in questo progetto. Verifica che il nome del file corrisponda a quello inserito nella funzione 'func decode<T: Decodable>(file: String) -> T' ")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Non è stato possibile recuperare i dati contenuti nel file \(file) di questo progetto")
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
guard let loadData = try? decoder.decode(T.self, from: data) else {
fatalError("Non è stato possibile decodificare il file \(file) in questo progetto")
}
return loadData
}
}
Вместо этого это класс менеджера Я использую app.quicktype.io
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
var profileV2: ProfileV2
enum CodingKeys: String, CodingKey {
case profileV2 = "profile_v2"
}
}
// MARK: - ProfileV2
struct ProfileV2: Codable {
var name: Name
var emails: Emails
var birthday: Birthday
var gender: Gender
var previousNames, otherNames: [JSONAny]
var currentCity, hometown: CurrentCity
var relationship: Relationship
var educationExperiences: [JSONAny]
var workExperiences: [WorkExperience]
var bloodInfo: BloodInfo
var websites: [JSONAny]
var screenNames: [ScreenName]
var phoneNumbers: [PhoneNumber]
var registrationTimestamp: Int
var profileURI: String
var introBio: CurrentCity
enum CodingKeys: String, CodingKey {
case name, emails, birthday, gender
case previousNames
case otherNames
case currentCity
case hometown, relationship
case educationExperiences
case workExperiences
case bloodInfo
case websites
case screenNames
case phoneNumbers
case registrationTimestamp
case profileURI
case introBio
}
static let root: ProfileV1 = Bundle.main.decode(file: "Profile.json")
static let infoUser: ProfileV2 = root.profileV2
}
// MARK: - Birthday
struct Birthday: Codable {
var year, month, day: Int
}
// MARK: - BloodInfo
struct BloodInfo: Codable {
var bloodDonorStatus: String
enum CodingKeys: String, CodingKey {
case bloodDonorStatus
}
}
// MARK: - CurrentCity
struct CurrentCity: Codable {
var name: String
var timestamp: Int
}
// MARK: - Emails
struct Emails: Codable {
var emails, previousEmails: [String]
var pendingEmails, adAccountEmails: [JSONAny]
enum CodingKeys: String, CodingKey {
case emails
case previousEmails
case pendingEmails
case adAccountEmails
}
}
// MARK: - Gender
struct Gender: Codable {
var genderOption, pronoun: String
enum CodingKeys: String, CodingKey {
case genderOption
case pronoun
}
}
// MARK: - Name
struct Name: Codable {
var fullName, firstName, middleName, lastName: String
enum CodingKeys: String, CodingKey {
case fullName
case firstName
case middleName
case lastName
}
}
// MARK: - PhoneNumber
struct PhoneNumber: Codable {
var phoneType, phoneNumber: String
var verified: Bool
enum CodingKeys: String, CodingKey {
case phoneType
case phoneNumber
case verified
}
}
// MARK: - Relationship
struct Relationship: Codable {
var status: String
var timestamp: Int
}
// MARK: - ScreenName
struct ScreenName: Codable {
var serviceName: String
var names: [CurrentCity]
enum CodingKeys: String, CodingKey {
case serviceName
case names
}
}
// MARK: - WorkExperience
struct WorkExperience: Codable {
var employer, title, location, description: String
var startTimestamp, timestamp: Int
enum CodingKeys: String, CodingKey {
case employer, title, location, description
case startTimestamp
case timestamp
}
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
class JSONCodingKey: CodingKey {
let key: String
required init?(intValue: Int) {
return nil
}
required init?(stringValue: String) {
key = stringValue
}
var intValue: Int? {
return nil
}
var stringValue: String {
return key
}
}
class JSONAny: Codable {
let value: Any
static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
return DecodingError.typeMismatch(JSONAny.self, context)
}
static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
return EncodingError.invalidValue(value, context)
}
static func decode(from container: SingleValueDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if container.decodeNil() {
return JSONNull()
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if let value = try? container.decodeNil() {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer() {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any {
if let value = try? container.decode(Bool.self, forKey: key) {
return value
}
if let value = try? container.decode(Int64.self, forKey: key) {
return value
}
if let value = try? container.decode(Double.self, forKey: key) {
return value
}
if let value = try? container.decode(String.self, forKey: key) {
return value
}
if let value = try? container.decodeNil(forKey: key) {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer(forKey: key) {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
var arr: [Any] = []
while !container.isAtEnd {
let value = try decode(from: &container)
arr.append(value)
}
return arr
}
static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
var dict = [String: Any]()
for key in container.allKeys {
let value = try decode(from: &container, forKey: key)
dict[key.stringValue] = value
}
return dict
}
static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
for value in array {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer()
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
for (key, value) in dictionary {
let key = JSONCodingKey(stringValue: key)!
if let value = value as? Bool {
try container.encode(value, forKey: key)
} else if let value = value as? Int64 {
try container.encode(value, forKey: key)
} else if let value = value as? Double {
try container.encode(value, forKey: key)
} else if let value = value as? String {
try container.encode(value, forKey: key)
} else if value is JSONNull {
try container.encodeNil(forKey: key)
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer(forKey: key)
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
public required init(from decoder: Decoder) throws {
if var arrayContainer = try? decoder.unkeyedContainer() {
self.value = try JSONAny.decodeArray(from: &arrayContainer)
} else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
self.value = try JSONAny.decodeDictionary(from: &container)
} else {
let container = try decoder.singleValueContainer()
self.value = try JSONAny.decode(from: container)
}
}
public func encode(to encoder: Encoder) throws {
if let arr = self.value as? [Any] {
var container = encoder.unkeyedContainer()
try JSONAny.encode(to: &container, array: arr)
} else if let dict = self.value as? [String: Any] {
var container = encoder.container(keyedBy: JSONCodingKey.self)
try JSONAny.encode(to: &container, dictionary: dict)
} else {
var container = encoder.singleValueContainer()
try JSONAny.encode(to: &container, value: self.value)
}
}
}
Я действительно не понимаю, почему загрузка данных JSON не осуществляется и поэтому автоматически возвращает мне мои fatal error, которые я ввел
@vadian Итак, вы советуете мне не использовать app.quicktype.io для генерации кода?
Не обязательно, но вместо JSONAny следует указывать реальные типы для пустых массивов. Мой главный совет — catch ошибку.
@vadian Что JSONAny - это класс, который реализовал app.quicktype.io в коде ... единственный способ сделать все по-другому - это вручную создать всю структуру шаблона, верно? вместо этого... Можете ли вы помочь мне с моим вопросом?
Каким был бы тип education_experiences, если бы он был заполнен?





Следующее работает нормально. Обратите внимание, что я использую код QuickType.io, но заменил тип JSONAny на String. Это может работать или не работать в зависимости от того, каковы фактические типы, когда в этих массивах есть значения...
Также обратите внимание, что делать подобные вещи на игровой площадке, прежде чем помещать их в ваш реальный код, — это отличный способ убедиться, что вы правильно кодируете/декодируете.
struct Welcome: Codable {
let profileV2: ProfileV2
}
struct ProfileV2: Codable {
let name: Name
let emails: Emails
let birthday: Birthday
let gender: Gender
let previousNames, otherNames: [String]
let currentCity, hometown: CurrentCity
let relationship: Relationship
let educationExperiences: [String]
let workExperiences: [WorkExperience]
}
struct Birthday: Codable {
let year, month, day: Int
}
struct CurrentCity: Codable {
let name: String
let timestamp: Int
}
struct Emails: Codable {
let emails, previousEmails: [String]
let pendingEmails, adAccountEmails: [String]
}
struct Gender: Codable {
let genderOption, pronoun: String
}
struct Name: Codable {
let fullName, firstName, middleName, lastName: String
}
struct Relationship: Codable {
let status: String
let timestamp: Int
}
struct WorkExperience: Codable {
let employer, title, location, description: String
let startTimestamp, timestamp: Int
}
let data = """
{
"profile_v2": {
"name": {
"full_name": "test test",
"first_name": "test",
"middle_name": "",
"last_name": "test"
},
"emails": {
"emails": [
"[email protected]"
],
"previous_emails": [
"[email protected]"
],
"pending_emails": [
],
"ad_account_emails": [
]
},
"birthday": {
"year": 33,
"month": 33,
"day": 24
},
"gender": {
"gender_option": "MALE",
"pronoun": "MALE"
},
"previous_names": [
],
"other_names": [
],
"current_city": {
"name": "test",
"timestamp": 0
},
"hometown": {
"name": "test",
"timestamp": 0
},
"relationship": {
"status": "",
"timestamp": 1574556291
},
"education_experiences": [
],
"work_experiences": [
{
"employer": "test test test test",
"title": "test",
"location": "test test test",
"description": "",
"start_timestamp": 1246442400,
"timestamp": 1589226310
}
]
}
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let foo = try decoder.decode(Welcome.self, from: data)
print(foo)
}
catch {
print(error)
}
Здравствуйте, и спасибо за ваше предложение, оно действительно правильное. Я немного изменил свой код, используя полный файл json (тот, что в моем вопросе, был лишь частью файла json, и, видимо, у меня проблема с переменной profile_uri, потому что сделать это работает я должен обязательно указать, что он типа String?, а не String я не понимаю, почему я должен вводить необязательный файл In `JSON` profile_uri содержит profile_uri": "https://www.facebook.com/user.user.50
По-видимому, он не всегда содержит строку. Иногда он либо отсутствует, либо содержит null. Поскольку это необязательно в json, оно должно быть необязательным и в Swift.
И если это должен быть URL-адрес, вы должны использовать URL? вместо String?
Спасибо, но если бы мне пришлось выяснить, может ли он быть NULL или не пустым, как я могу понять это, просто изучив JSON file? Это невозможно? Также однажды у меня есть let foo = try decoder.decode(Welcome.self, from: data). константа foo, которая содержит все элементы JSON file, как я могу ее использовать? Создание array, содержащего элементы foo, не кажется мне хорошей идеей, так как в этом случае я должен получать значения JSON по отдельности.
Вы должны посмотреть в json и посмотреть, отсутствуют ли какие-либо объекты с profile_url или есть, но он говорит "profile_url": null. Что касается того, как работать с foo... Это структура. Вы работаете с ним, как и с любой структурой. Я предлагаю вам принять этот ответ, а затем задать новый вопрос, если он у вас есть.
Я понимаю, что я думал о создании диспетчера классов и в его инициализации декодировать файл, а затем вызывать данные с переменной, но этот подход не кажется правильным
Пожалуйста, замените
try?наtryв блокеdo - catchиprint(error). ТакиеfatalErrorбессмысленны. ИJSONAny— с точки зрения производительности — ужасная попытка поддержатьAny. Я думаю, что традиционныйJSONSerializtionболее эффективен в этом случае.