Ошибка декодирования JSON Swift

Я знаю, что это второй раз, когда я спрашиваю о 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, которые я ввел

Пожалуйста, замените try? на try в блоке do - catch и print(error). Такие fatalError бессмысленны. И JSONAny — с точки зрения производительности — ужасная попытка поддержать Any. Я думаю, что традиционный JSONSerializtion более эффективен в этом случае.

vadian 01.05.2023 19:56

@vadian Итак, вы советуете мне не использовать app.quicktype.io для генерации кода?

kAiN 01.05.2023 20:02

Не обязательно, но вместо JSONAny следует указывать реальные типы для пустых массивов. Мой главный совет — catch ошибку.

vadian 01.05.2023 20:15

@vadian Что JSONAny - это класс, который реализовал app.quicktype.io в коде ... единственный способ сделать все по-другому - это вручную создать всю структуру шаблона, верно? вместо этого... Можете ли вы помочь мне с моим вопросом?

kAiN 01.05.2023 20:18

Каким был бы тип education_experiences, если бы он был заполнен?

Daniel T. 01.05.2023 21:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Следующее работает нормально. Обратите внимание, что я использую код 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

kAiN 02.05.2023 11:39

По-видимому, он не всегда содержит строку. Иногда он либо отсутствует, либо содержит null. Поскольку это необязательно в json, оно должно быть необязательным и в Swift.

Daniel T. 02.05.2023 13:14

И если это должен быть URL-адрес, вы должны использовать URL? вместо String?

Daniel T. 02.05.2023 13:16

Спасибо, но если бы мне пришлось выяснить, может ли он быть NULL или не пустым, как я могу понять это, просто изучив JSON file? Это невозможно? Также однажды у меня есть let foo = try decoder.decode(Welcome.self, from: data). константа foo, которая содержит все элементы JSON file, как я могу ее использовать? Создание array, содержащего элементы foo, не кажется мне хорошей идеей, так как в этом случае я должен получать значения JSON по отдельности.

kAiN 02.05.2023 13:43

Вы должны посмотреть в json и посмотреть, отсутствуют ли какие-либо объекты с profile_url или есть, но он говорит "profile_url": null. Что касается того, как работать с foo... Это структура. Вы работаете с ним, как и с любой структурой. Я предлагаю вам принять этот ответ, а затем задать новый вопрос, если он у вас есть.

Daniel T. 02.05.2023 15:41

Я понимаю, что я думал о создании диспетчера классов и в его инициализации декодировать файл, а затем вызывать данные с переменной, но этот подход не кажется правильным

kAiN 02.05.2023 16:05

Другие вопросы по теме