Як серіалізувати або перетворити об'єкти Swift в JSON?


84

Це нижче класу

class User: NSManagedObject {
  @NSManaged var id: Int
  @NSManaged var name: String
}

Потрібно перетворити на

{
    "id" : 98,
    "name" : "Jon Doe"
}

Я спробував вручну передати об'єкт функції, яка встановлює змінні в словник і повертає словник. Але я хотів би кращого способу досягти цього.


1
Найкраще запустити його і зберегти в масиви та дикти, а потім конвертувати.
Схематичний

Посилайтеся на це - github.com/dankogai/swift-json
Uttam Sinha

@Schemetrical не могли б ви вказати мені приклад? Я не знаю, як пробігти об'єкт.
Пенкі Суреш,

Ну, вам потрібно пройти сам об'єкт, взяти всі значення, і додати його до дикту вручну, і повторити.
Схематично

@Schemetrical Я насправді спробував це. Але для великих об'єктів час компіляції різко збільшується.
Пенкі Суреш,

Відповіді:


130

У Swift 4 ви можете успадкувати від Codableтипу.

struct Dog: Codable {
    var name: String
    var owner: String
}

// Encode
let dog = Dog(name: "Rex", owner: "Etgar")

let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(dog)
let json = String(data: jsonData, encoding: String.Encoding.utf16)

// Decode
let jsonDecoder = JSONDecoder()
let secondDog = try jsonDecoder.decode(Dog.self, from: jsonData)

26
Тип кодування повинен бути .utf8 замість .utf16
Чан Цзин Гонг

2
@ChanJingHong це залежить від того, що ви намагаєтесь закодувати
Етгар,

Для цього конкретного прикладу працює .utf16? Я намагався, але це не працює.
Чан Цзін Хонг

@ChanJingHong Дивно, я спробував це 3 місяці тому, і це спрацювало. Я думаю, що я повинен був вказати тип кодування також як аргумент у методі декодування. Я перевірю це.
Етгар

2
питання стосувалося конкретно кодування class(що мені потрібно), а не struct.
гондо

39

Поряд із Swift 4 (Foundation) тепер він підтримується в обох напрямках, рядок JSON до об'єкта - об'єкт до рядка JSON. Будь ласка, ознайомтесь з документацією Apple тут JSONDecoder () і тут JSONEncoder ()

JSON рядок для об'єкта

let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let myStruct = try! decoder.decode(myStruct.self, from: jsonData)

Швидкий об'єкт до JSONString

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try! encoder.encode(myStruct)
print(String(data: data, encoding: .utf8)!)

Всі подробиці та приклади ви можете знайти тут Кінцевий посібник з розбору JSON за допомогою Swift 4


25

ОНОВЛЕННЯ: Codable протоколу, введеного в Swift 4, має бути достатньо для більшості JSONвипадків розбору. Нижче наведено відповідь для людей, які застрягли в попередніх версіях Swift та з застарілих причин

EVReflection :

  • Це працює за принципом рефлексії. Це вимагає менше коду , а також підтримує NSDictionary, NSCoding, Printable, HashableіEquatable

Приклад:

    class User: EVObject { # extend EVObject method for the class
       var id: Int = 0
       var name: String = ""
       var friends: [User]? = []
    }

    # use like below
    let json:String = "{\"id\": 24, \"name\": \"Bob Jefferson\", \"friends\": [{\"id\": 29, \"name\": \"Jen Jackson\"}]}"
    let user = User(json: json)

ObjectMapper :

  • Інший спосіб - використання ObjectMapper. Це дає більше контролю, але також вимагає набагато більше коду.

Приклад:

    class User: Mappable { # extend Mappable method for the class
       var id: Int?
       var name: String?

       required init?(_ map: Map) {

       }

       func mapping(map: Map) { # write mapping code
          name    <- map["name"]
          id      <- map["id"]
       }

    }

    # use like below
    let json:String = "{\"id\": 24, \"name\": \"Bob Jefferson\", \"friends\": [{\"id\": 29, \"name\": \"Jen Jackson\"}]}"
    let user = Mapper<User>().map(json)

Чи підтримує він також відображення зображень ??
Чарлі

1
@Charlie жаль, я не впевнений, що це можливо.
Penkey Suresh

1
@ Suresh: Це можливо, написавши власну трансформацію, як показано на прикладах. Я перетворив зображення на String, а потім додав в об'єкт Json. Дуже допомагає спеціальна робота з годинниковими механізмами
Charlie

1
Привіт, ти знаєш, як ініціалізувати клас Mappable та встановити властивості вручну, а потім перетворити об’єкт на jsonString?
VAAA,

який вплив кодується протоколу від swift 4 / ios 11 ?? . Чи можемо ми швидко перетворити NSManagedObject на JSON за допомогою цього ??
user1828845

13

Я трохи працював над меншим рішенням, яке не вимагає успадкування. Але це ще не багато випробувано. Це досить потворний банкомат.

https://github.com/peheje/JsonSerializerSwift

Ви можете передати його на дитячий майданчик, щоб перевірити. Наприклад, така структура класу:

//Test nonsense data
class Nutrient {
    var name = "VitaminD"
    var amountUg = 4.2

    var intArray = [1, 5, 9]
    var stringArray = ["nutrients", "are", "important"]
}

class Fruit {
    var name: String = "Apple"
    var color: String? = nil
    var weight: Double = 2.1
    var diameter: Float = 4.3
    var radius: Double? = nil
    var isDelicious: Bool = true
    var isRound: Bool? = nil
    var nullString: String? = nil
    var date = NSDate()

    var optionalIntArray: Array<Int?> = [1, 5, 3, 4, nil, 6]
    var doubleArray: Array<Double?> = [nil, 2.2, 3.3, 4.4]
    var stringArray: Array<String> = ["one", "two", "three", "four"]
    var optionalArray: Array<Int> = [2, 4, 1]

    var nutrient = Nutrient()
}

var fruit = Fruit()
var json = JSONSerializer.toJson(fruit)

print(json)

відбитки

{"name": "Apple", "color": null, "weight": 2.1, "diameter": 4.3, "radius": null, "isDelicious": true, "isRound": null, "nullString": null, "date": "2015-06-19 22:39:20 +0000", "optionalIntArray": [1, 5, 3, 4, null, 6], "doubleArray": [null, 2.2, 3.3, 4.4], "stringArray": ["one", "two", "three", "four"], "optionalArray": [2, 4, 1], "nutrient": {"name": "VitaminD", "amountUg": 4.2, "intArray": [1, 5, 9], "stringArray": ["nutrients", "are", "important"]}}

Ви можете надати його швидку версію 2.3?
H Raval

8

Це не ідеальне / автоматичне рішення, але я вважаю, що це ідіоматичний і рідний спосіб зробити це. Таким чином, вам не потрібні будь-які бібліотеки тощо.

Створіть такий протокол, як:

/// A generic protocol for creating objects which can be converted to JSON
protocol JSONSerializable {
    private var dict: [String: Any] { get }
}

extension JSONSerializable {
    /// Converts a JSONSerializable conforming class to a JSON object.
    func json() rethrows -> Data {
        try JSONSerialization.data(withJSONObject: self.dict, options: nil)
    }
}

Потім застосуйте його у своєму класі, наприклад:

class User: JSONSerializable {
    var id: Int
    var name: String

    var dict { return ["id": self.id, "name": self.name]  }
}

Зараз:

let user = User(...)
let json = user.json()

Примітка: якщо ви хочете jsonяк рядок, дуже просто перетворити на рядок:String(data: json, encoding .utf8)


6

Деякі з наведених вище відповідей цілком чудові, але я додав сюди розширення, щоб зробити його набагато читальнішим та кориснішим.

extension Encodable {
    var convertToString: String? {
        let jsonEncoder = JSONEncoder()
        jsonEncoder.outputFormatting = .prettyPrinted
        do {
            let jsonData = try jsonEncoder.encode(self)
            return String(data: jsonData, encoding: .utf8)
        } catch {
            return nil
        }
    }
}

struct User: Codable {
     var id: Int
     var name: String
}

let user = User(id: 1, name: "name")
print(user.convertToString!)

// Це буде надруковано наступним чином:

{
  "id" : 1,
  "name" : "name"
}

2

Не впевнений, що існує lib / framework, але якщо ви хочете зробити це автоматично і хочете уникати ручної праці :-) дотримуйтесь MirrorType...

class U {

  var id: Int
  var name: String

  init(id: Int, name: String) {
    self.id = id
    self.name = name
  }

}

extension U {

  func JSONDictionary() -> Dictionary<String, Any> {
    var dict = Dictionary<String, Any>()

    let mirror = reflect(self)

    var i: Int
    for i = 0 ; i < mirror.count ; i++ {
      let (childName, childMirror) = mirror[i]

      // Just an example how to check type
      if childMirror.valueType is String.Type {
        dict[childName] = childMirror.value
      } else if childMirror.valueType is Int.Type {
        // Convert to NSNumber for example
        dict[childName] = childMirror.value
      }
    }

    return dict
  }

}

Візьміть це як приблизний приклад, йому не вистачає належної підтримки перетворення, бракує рекурсії, ... Це лише MirrorTypeдемонстрація ...

PS Тут це зроблено в U, але ви збираєтеся вдосконалити, NSManagedObjectі тоді ви зможете перетворити всі NSManagedObjectпідкласи. Не потрібно реалізовувати це у всіх підкласах / керованих об'єктах.


Привіт, я пробую цей метод, але щоразу, коли отримую 0 як рахунок. Чи можете ви більш точно визначити, що я повинен робити? Я думаю, що @NSManaged викликає проблеми. Як покращити NSManagedObject?
Пенкі Суреш

Не намагався @NSManaged. Можливо, це спричиняє вашу проблему. У цьому випадку я писав би це в Objective-C, а потім використовував би його від Swift.
zrzka

0

2020 | SWIFT 5.1:

(також працює з SWIFT 4)


Готове до роботи рішення!

Використання:

var msgTemplates = [msgTemlate]()

// load from file
msgTemplates = try! Serializer.load(from: url)!

// save to file
Serializer.save(data: msgTemplates, to: url)

Наступний код вирішує 3 речі:

  • 1 рядок для збереження файлу
  • 1 рядок для завантаження файлу
  • можливість отримати / роздрукувати JSON якогось елемента Codable за допомогою element.toJsonString
    import Foundation

    public class Serializer{
        static func save<T>(data: T, to url: URL) where T : Encodable{
            guard let json = data.toJsonString else { return }

            do {
                try json.write(to: url, atomically: true, encoding: String.Encoding.utf8)
            }
            catch { /* error handling here */ }
        }

        static func load<T>(from url: URL) throws -> T? where T : Decodable {
            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .iso8601 // for human-read date format

            guard let dataStr = try? String(contentsOf: url, encoding: String.Encoding.utf8 ),
                  let data = dataStr.data(using: String.Encoding.utf8 ),
                  let result = try? decoder.decode( T.self , from: data)
            else { return nil }

            return result
        }
    }

    extension Encodable {
        var toJsonString: String? {
            let encoder = JSONEncoder()
            encoder.outputFormatting = .prettyPrinted  // nice formatted for reading by human
            encoder.dateEncodingStrategy = .iso8601    // for human-read date format

            do {
                let jsonData = try encoder.encode(self)
                return String(data: jsonData, encoding: .utf8)
            } catch {
                return nil
            }
        }
    }

PS: і дані ofc повинні бути кодованими:

struct msgTemlate: Codable { 
     //some params
}

PS2: у випадку, якщо у msgTemlate є переліки, вони також повинні бути Codable


0
struct User:Codable{
 var id:String?
 var name:String?
 init(_ id:String,_ name:String){
   self.id  = id
   self.name = name
 }
}

Тепер просто зробіть свій об’єкт таким

let user = User ("1", "pawan")

do{
      let userJson =  try JSONEncoder().encode(parentMessage) 
            
    }catch{
         fatalError("Unable To Convert in Json")      
    }

Потім перетворити з json на Object

let jsonDecoder = JSONDecoder()
do{
   let convertedUser = try jsonDecoder.decode(User.self, from: userJson.data(using: .utf8)!)
 }catch{
   
 }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.