Avatar ·

Преобразование словаря в JSON в Swift: пошаговое руководство

Всем привет! Я новичок в Swift и пытаюсь разобраться с JSON. Мне нужно отправить данные на сервер в формате JSON, и я храню их в виде словаря `[String: Any]`. Например, у меня есть такой словарь: ```swift let userDict: [String: Any] = [ "name": "Иван", "age": 30, "isActive": true, "address": [ "city": "Москва", "zip": "101000" ] ] ``` Я пробовал использовать `JSONSerialization`, но постоянно получаю ошибки. Вот мой код: ```swift let jsonData = try JSONSerialization.data(withJSONObject: userDict, options: .prettyPrinted) ``` Ошибка: `"Invalid value around key 'age'."` — не понимаю, почему так происходит. Я читал документацию, но она кажется сложной. Подскажите, как правильно конвертировать словарь в JSON? Нужно ли как-то проверять типы данных? И как потом превратить `Data` обратно в словарь? Буду благодарен за примеры с объяснениями. <<>>

Основная проблема и её решение

Ваша ошибка возникает из-за того, что JSONSerialization.data(withJSONObject:options:) требует, чтобы все значения в словаре были JSON-совместимыми. В Swift числовые типы (Int, Double), строки, булевы значения и массивы/словари с такими же типами — всё ок. Но если вы случайно передали nil или нестандартный тип (например, Date), то получите ошибку.

В вашем коде всё выглядит корректно, но давайте разберём правильный подход шаг за шагом.

Шаг 1: Базовое преобразование с обработкой ошибок

import Foundation

let userDict: [String: Any] = [
    "name": "Иван",
    "age": 30,
    "isActive": true,
    "address": [
        "city": "Москва",
        "zip": "101000"
    ]
]

do {
    let jsonData = try JSONSerialization.data(withJSONObject: userDict, options: .prettyPrinted)
    // Теперь jsonData можно отправить на сервер или сохранить
    if let jsonString = String(data: jsonData, encoding: .utf8) {
        print(jsonString)
    }
} catch {
    print("Ошибка сериализации: \(error.localizedDescription)")
}

Этот код выведет отформатированный JSON:

{
  "name" : "Иван",
  "age" : 30,
  "isActive" : true,
  "address" : {
    "city" : "Москва",
    "zip" : "101000"
  }
}

Шаг 2: Обратное преобразование (JSON → словарь)

let jsonString = """
{
  "name" : "Иван",
  "age" : 30,
  "isActive" : true
}
"""

if let jsonData = jsonString.data(using: .utf8) {
    do {
        if let dict = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
            print(dict["name"] as? String ?? "")
        }
    } catch {
        print("Ошибка десериализации: \(error)")
    }
}

Важные моменты

  • Типы данных: Убедитесь, что все значения — String, Int, Double, Bool, [Any], [String: Any] или nil (который станет null). Если есть Date, преобразуйте его в строку через ISO8601DateFormatter.
  • Опции форматирования: .prettyPrinted добавляет отступы для читаемости, .sortedKeys сортирует ключи (полезно для тестирования).
  • Безопасность: Всегда оборачивайте вызов в do-catch — сериализация может выбросить исключение.
  • Альтернатива с Codable: Если у вас фиксированная структура данных, лучше использовать протокол Codable — это безопаснее и удобнее.

Пример с Codable (рекомендуемый подход для сложных данных)

struct User: Codable {
    let name: String
    let age: Int
    let isActive: Bool
    let address: Address
}

struct Address: Codable {
    let city: String
    let zip: String
}

let user = User(name: "Иван", age: 30, isActive: true, 
                address: Address(city: "Москва", zip: "101000"))

do {
    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted
    let jsonData = try encoder.encode(user)
    print(String(data: jsonData, encoding: .utf8)!)
} catch {
    print("Ошибка: \(error)")
}

Так вы избегаете проблем с типами и получаете строгую типизацию на уровне компилятора.

<<>>

Упрощённый подход с JSONSerialization и проверкой типов

Если вы не хотите использовать Codable (например, для динамических данных), можно написать вспомогательную функцию, которая рекурсивно проверяет значения:

func safeJSONObject(from dict: [String: Any]) -> [String: Any] {
    var result = [String: Any]()
    for (key, value) in dict {
        switch value {
        case is String, is Int, is Double, is Bool, is NSNull:
            result[key] = value
        case let nestedDict as [String: Any]:
            result[key] = safeJSONObject(from: nestedDict)
        case let nestedArray as [Any]:
            result[key] = nestedArray.map { element in
                if let dictElement = element as? [String: Any] {
                    return safeJSONObject(from: dictElement)
                }
                return element
            }
        default:
            // Если тип не поддерживается, преобразуем в строку
            result[key] = "\(value)"
        }
    }
    return result
}

// Использование
let cleanDict = safeJSONObject(from: userDict)
let jsonData = try JSONSerialization.data(withJSONObject: cleanDict, options: .prettyPrinted)

Этот код автоматически заменяет неподдерживаемые типы (например, Date) на их строковое представление. Однако помните: для production-кода лучше использовать Codable или явно преобразовывать каждый тип. Данный метод — быстрый костыль для прототипов.