Jak serializować lub konwertować obiekty Swift na JSON?

84

To poniżej klasy

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

Musi zostać przekonwertowany na

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

Próbowałem ręcznie przekazać obiekt do funkcji, która ustawia zmienne w słowniku i zwraca słownik. Ale chciałbym mieć lepszy sposób, aby to osiągnąć.

Penkey Suresh
źródło
1
Najlepiej jest przejrzeć go i zapisać w tablicach i dyktach, a następnie przekonwertować.
Schematyczny
Skieruj
Uttam Sinha
@Schemetrical czy możesz wskazać mi przykład? Nie wiem, jak przebiec obiekt.
Penkey Suresh
Cóż, musisz przejść przez sam obiekt, wziąć wszystkie wartości i ręcznie dodać je do dyktu i powtórzyć.
Schematyczny
@Schemetrical Właściwie to próbowałem. Jednak w przypadku dużych obiektów czas kompilacji znacznie się wydłuża.
Penkey Suresh

Odpowiedzi:

130

W Swift 4 możesz dziedziczyć po Codabletypie.

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)
Etgar
źródło
26
Typ kodowania powinien być .utf8 zamiast .utf16
Chan Jing Hong,
2
@ChanJingHong to zależy od tego, co próbujesz zakodować
Etgar
Czy w tym konkretnym przykładzie działa .utf16? Próbowałem, ale to nie działa.
Chan Jing Hong
@ChanJingHong Strange, próbowałem 3 miesiące temu i zadziałało. Myślę, że powinienem był umieścić typ kodowania również jako argument w metodzie dekodowania. Sprawdzę to.
Etgar
2
pytanie dotyczyło szczególnie kodowania class(czego potrzebuję), a nie struct.
gondo
39

Wraz ze Swift 4 (Foundation) jest teraz natywnie obsługiwany na oba sposoby, ciąg JSON do obiektu - obiekt do ciągu JSON. Zobacz dokumentację Apple tutaj JSONDecoder () i tutaj JSONEncoder ()

Ciąg JSON do obiektu

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

Swift Object do JSONString

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

Wszystkie szczegóły i przykłady można znaleźć tutaj. Ultimate Guide to Parsing JSON With Swift 4

mohaki
źródło
25

AKTUALIZACJA: Codable protokół wprowadzony w Swift 4 powinien wystarczyć w większości JSONprzypadków analizowania. Poniższa odpowiedź jest przeznaczona dla osób, które utknęły w poprzednich wersjach Swifta iz powodów starszych

EVReflection :

  • To działa na zasadzie refleksji. Trwa to mniej kodu, a także wspiera NSDictionary, NSCoding, Printable, HashableiEquatable

Przykład:

    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 :

  • Innym sposobem jest użycie ObjectMapper. Daje to większą kontrolę, ale także wymaga znacznie więcej kodu.

Przykład:

    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)
Penkey Suresh
źródło
Czy obsługuje również mapowanie obrazów?
Charlie
1
@Charlie sorry, nie jestem pewien, czy to możliwe.
Penkey Suresh
1
@ Suresh: Jest to możliwe, pisząc niestandardową transformację, jak pokazano na przykładach. Przekonwertowałem obraz na String, a następnie dodałem do obiektu Json. Bardzo pomaga szczególnie praca z systemem operacyjnym zegarka
Charlie,
1
Cześć, czy wiesz, jak zainicjować klasę Mappable i ręcznie ustawić właściwości, a następnie przekonwertować obiekt na jsonString?
VAAA
jaki jest wpływ kodowalnego protokołu ze Swift 4 / iOS 11? . Czy możemy przekonwertować NSManagedObject na JSON w Swift 4 używając tego?
user1828845
13

Pracowałem trochę nad mniejszym rozwiązaniem, które nie wymaga dziedziczenia. Ale nie zostało to zbytnio przetestowane. To dość brzydki bankomat.

https://github.com/peheje/JsonSerializerSwift

Możesz przekazać go na plac zabaw, aby go przetestować. Np. Następująca struktura klas:

//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)

wydruki

{"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"]}}
Peheje
źródło
czy możesz podać jego szybką wersję 2.3?
H Raval
8

To nie jest idealne / automatyczne rozwiązanie, ale uważam, że jest to idiomatyczny i rodzimy sposób, aby to zrobić. W ten sposób nie potrzebujesz żadnych bibliotek ani tym podobnych.

Utwórz protokół, taki jak:

/// 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)
    }
}

Następnie zaimplementuj to w swojej klasie, na przykład:

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

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

Teraz:

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

Uwaga: jeśli chcesz jsonjako ciąg, bardzo łatwo jest przekonwertować na ciąg:String(data: json, encoding .utf8)

Downgoat
źródło
6

Niektóre z powyższych odpowiedzi są całkowicie w porządku, ale dodałem tutaj rozszerzenie, aby było bardziej czytelne i użyteczne.

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!)

// To wydrukuje się w następujący sposób:

{
  "id" : 1,
  "name" : "name"
}
dheeru
źródło
2

Nie jestem pewien, czy istnieje biblioteka / framework, ale jeśli chciałbyś to zrobić automatycznie i chciałbyś uniknąć pracy fizycznej :-) trzymaj się 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
  }

}

Potraktuj to jako przybliżony przykład, brak odpowiedniej obsługi konwersji, brak rekursji, ... To tylko MirrorTypedemonstracja ...

PS Tutaj jest to zrobione U, ale zamierzasz ulepszyć NSManagedObjecti wtedy będziesz mógł przekonwertować wszystkie NSManagedObjectpodklasy. Nie ma potrzeby implementowania tego we wszystkich podklasach / obiektach zarządzanych.

zrzka
źródło
Cześć, próbuję tej metody, ale za każdym razem otrzymuję 0 jako liczbę. Czy mógłbyś sprecyzować, co mam zrobić? Myślę, że @NSManaged powoduje problemy. Jak ulepszyć NSManagedObject?
Penkey Suresh
Nie próbowałem @NSManaged. Może to powoduje twój problem. W tym przypadku zapisałbym to w Objective-C, a następnie użyję go z Swift.
zrzka
0

2020 | SWIFT 5.1:

(działa również z SWIFT 4)


Gotowe do pracy rozwiązanie!

Stosowanie:

var msgTemplates = [msgTemlate]()

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

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

Poniższy kod rozwiązuje 3 rzeczy:

  • 1 ciąg do zapisania pliku
  • 1 ciąg do załadowania pliku
  • możliwość pobrania / wydrukowania JSON jakiegoś elementu Codable z 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: i dane ofc muszą być kodowalne:

struct msgTemlate: Codable { 
     //some params
}

PS2: w przypadku, gdy msgTemlate ma wyliczenia, muszą one również być kodowane

Andrzej
źródło
0
struct User:Codable{
 var id:String?
 var name:String?
 init(_ id:String,_ name:String){
   self.id  = id
   self.name = name
 }
}

Teraz po prostu stwórz swój obiekt w ten sposób

let user = użytkownik ("1", "pawan")

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

Następnie przekonwertuj ponownie z json na Object

let jsonDecoder = JSONDecoder()
do{
   let convertedUser = try jsonDecoder.decode(User.self, from: userJson.data(using: .utf8)!)
 }catch{
   
 }
Pawan Kumar Sharma
źródło