Prosty i czysty sposób na konwersję ciągu JSON do Object w języku Swift

85

Szukałem dni, aby przekonwertować dość prosty ciąg JSON na typ obiektu w Swift, ale bezskutecznie.

Oto kod połączenia z usługą internetową:

func GetAllBusiness() {

        Alamofire.request(.GET, "http://MyWebService/").responseString { (request, response, string, error) in

                println(string)

        }
}

Mam szybką strukturę Business.swift:

struct Business {
    var Id : Int = 0
    var Name = ""
    var Latitude = ""
    var Longitude = ""
    var Address = ""
}

Oto moja wdrożona usługa testowa:

[
  {
    "Id": 1,
    "Name": "A",
    "Latitude": "-35.243256",
    "Longitude": "149.110701",
    "Address": null
  },
  {
    "Id": 2,
    "Name": "B",
    "Latitude": "-35.240592",
    "Longitude": "149.104843",
    "Address": null
  }
  ...
]

Byłoby wspaniale, gdyby ktoś mnie przez to przeprowadził.

Dzięki.

Hasan Nizamani
źródło

Odpowiedzi:

56

Oto kilka wskazówek, jak zacząć od prostego przykładu.

Rozważ, że masz następujący ciąg JSON Array (podobny do twojego), taki jak:

 var list:Array<Business> = []

  // left only 2 fields for demo
  struct Business {
    var id : Int = 0
    var name = ""               
 }

 var jsonStringAsArray = "[\n" +
        "{\n" +
        "\"id\":72,\n" +
        "\"name\":\"Batata Cremosa\",\n" +            
        "},\n" +
        "{\n" +
        "\"id\":183,\n" +
        "\"name\":\"Caldeirada de Peixes\",\n" +            
        "},\n" +
        "{\n" +
        "\"id\":76,\n" +
        "\"name\":\"Batata com Cebola e Ervas\",\n" +            
        "},\n" +
        "{\n" +
        "\"id\":56,\n" +
        "\"name\":\"Arroz de forma\",\n" +            
    "}]"


        // convert String to NSData
        var data: NSData = jsonStringAsArray.dataUsingEncoding(NSUTF8StringEncoding)!
        var error: NSError?

        // convert NSData to 'AnyObject'
        let anyObj: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0),
            error: &error)
        println("Error: \(error)")

     // convert 'AnyObject' to Array<Business>
     list = self.parseJson(anyObj!)

     //===============

    func parseJson(anyObj:AnyObject) -> Array<Business>{

        var list:Array<Business> = []

         if  anyObj is Array<AnyObject> {

            var b:Business = Business()

            for json in anyObj as Array<AnyObject>{
             b.name = (json["name"] as AnyObject? as? String) ?? "" // to get rid of null
             b.id  =  (json["id"]  as AnyObject? as? Int) ?? 0                 

               list.append(b)
            }// for

        } // if

      return list

    }//func    

[EDYTOWAĆ]

Aby pozbyć się wartości null, zmieniono na:

b.name = (json["name"] as AnyObject? as? String) ?? ""
b.id  =  (json["id"]  as AnyObject? as? Int) ?? 0 

Zobacz także odniesienie do Coalescing Operator(aka ??)

Mam nadzieję, że pomoże Ci to rozwiązać,

Maxim Shoustin
źródło
Niesamowite! Działał jak urok. Dzięki! Tylko jedna drobiazg, to błąd, jeśli element w JSON jest pusty. Jak w: b.name = json ["name"] as AnyObject! as String Jeśli nazwa ma wartość null, w jaki sposób mogę dodać warunek, aby dopuszczał wartość null?
Hasan Nizamani
jaka jest potrzeba rzutowania na AnyObject przed rzutowaniem na String?
Bateramos
@Bateramos nic. Otrzymujesz opcjonalne za AnyObjectpomocą klucza, po prostu upewnij się, przed obniżeniem do String, że tak nie jest nil. Z tego powodu możesz incapsualte używając !lub w moim przypadku ?z holdplace??
Maxim Shoustin
Myślę, że jeśli utworzysz obiekt var b:Business = Business()poza pętlą boczną, może to pokazać te same dane w każdym elemencie listy.
Patriks
51

dla szybkiego 3/4

extension String {
    func toJSON() -> Any? {
        guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil }
        return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
    }
}

Przykładowe zastosowanie:

 let dict = myString.toJSON() as? [String:AnyObject] // can be any type here
Tylko programista
źródło
2
Przyszły przypis: zamiast do-catch, try?można tu użyć, co da ten sam wynik, co zwrócenie zera w catch.
Okhan Okbay
... a jak dokładnie uzyskuje się dostęp do parametru po tej konwersji?
Starwave
Dla wszystkich zainteresowanych: niech jsonObjectAsNSDictionary = responseString? .ToJSON () as! [String: AnyObject] print (jsonObjectAsNSDictionary ["permissions"]! ["Canaddeditowncomment"])
Starwave
1
Popraw szybką składnię ... extension String { func toJSON() -> Any? { guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil } return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) } }
Yasir Ali
27

Jako proste rozszerzenie String powinno wystarczyć:

extension String {

    var parseJSONString: AnyObject? {

        let data = self.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)

        if let jsonData = data {
            // Will return an object or nil if JSON decoding fails
            return NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers, error: nil)
        } else {
            // Lossless conversion of the string was not possible
            return nil
        }
    }
}

Następnie:

var jsonString = "[\n" +
    "{\n" +
    "\"id\":72,\n" +
    "\"name\":\"Batata Cremosa\",\n" +            
    "},\n" +
    "{\n" +
    "\"id\":183,\n" +
    "\"name\":\"Caldeirada de Peixes\",\n" +            
    "},\n" +
    "{\n" +
    "\"id\":76,\n" +
    "\"name\":\"Batata com Cebola e Ervas\",\n" +            
    "},\n" +
    "{\n" +
    "\"id\":56,\n" +
    "\"name\":\"Arroz de forma\",\n" +            
"}]"

let json: AnyObject? = jsonString.parseJSONString
println("Parsed JSON: \(json!)")
println("json[3]: \(json![3])")

/* Output:

Parsed JSON: (
    {
    id = 72;
    name = "Batata Cremosa";
    },
    {
    id = 183;
    name = "Caldeirada de Peixes";
    },
    {
    id = 76;
    name = "Batata com Cebola e Ervas";
    },
    {
    id = 56;
    name = "Arroz de forma";
    }
)

json[3]: {
    id = 56;
    name = "Arroz de forma";
}
*/
PassKit
źródło
15

Swift 4 analizuje JSON znacznie bardziej elegancko. Po prostu zastosuj kodowalny protokół dla swojej struktury, zgodnie z tym uproszczonym przykładem:

struct Business: Codable {
    let id: Int
    let name: String
}

Aby przeanalizować tablicę JSON, należy poinformować dekoder, jakie są obiekty tablicy danych

let parsedData = decoder.decode([Business].self, from: data)

Oto pełny przykład roboczy:

import Foundation

struct Business: Codable {
    let id: Int
    let name: String
}

// Generating the example JSON data: 
let originalObjects = [Business(id: 0, name: "A"), Business(id: 1, name: "B")]
let encoder = JSONEncoder()
let data = try! encoder.encode(originalObjects)

// Parsing the data: 
let decoder = JSONDecoder()
let parsedData = try! decoder.decode([Business].self, from: data)

Więcej informacji znajdziesz w tym doskonałym przewodniku .

Noyer282
źródło
Wadą tego jest to, że trzeba śledzić strukturę, a jeśli masz ~ 30 lub więcej parametrów, zarządzanie stanie się dużym kłopotem.
Starwave
@Starwave: Nie jestem pewien, czy to rozwiązuje Twoje obawy, ale pamiętaj, że w strukturze używanej do dekodowania danych wystarczy uwzględnić tylko te pola, na których Ci zależy. Np. Jeśli JSON miałby postać [{"id": 1, "name:" A "," location ":" Warsaw "},] nadal można by go zdekodować przy użyciu tej samej struktury Business. Pole lokalizacji byłoby po prostu ignorowane
Noyer282
Do diabła, nie pomyślałem o tym ... Ale w takim razie, jakie są tutaj przeanalizowane dane? NSDictionary?
Starwave
@Starwave: To tablica struktur, tak jak w pierwotnym pytaniu.
Noyer282
9

Dla Swift 4

Użyłem logiki @ Passkit , ale musiałem zaktualizować zgodnie ze Swift 4


Krok 1 Utworzono rozszerzenie dla klasy String

import UIKit


extension String
    {
        var parseJSONString: AnyObject?
        {
            let data = self.data(using: String.Encoding.utf8, allowLossyConversion: false)

            if let jsonData = data
            {
                // Will return an object or nil if JSON decoding fails
                do
                {
                    let message = try JSONSerialization.jsonObject(with: jsonData, options:.mutableContainers)
                    if let jsonResult = message as? NSMutableArray
                    {
                        print(jsonResult)

                        return jsonResult //Will return the json array output
                    }
                    else
                    {
                        return nil
                    }
                }
                catch let error as NSError
                {
                    print("An error occurred: \(error)")
                    return nil
                }
            }
            else
            {
                // Lossless conversion of the string was not possible
                return nil
            }
        }
    }

Krok 2 Oto jak użyłem w moim kontrolerze widoku

var jsonString = "[\n" +
    "{\n" +
    "\"id\":72,\n" +
    "\"name\":\"Batata Cremosa\",\n" +            
    "},\n" +
    "{\n" +
    "\"id\":183,\n" +
    "\"name\":\"Caldeirada de Peixes\",\n" +            
    "},\n" +
    "{\n" +
    "\"id\":76,\n" +
    "\"name\":\"Batata com Cebola e Ervas\",\n" +            
    "},\n" +
    "{\n" +
    "\"id\":56,\n" +
    "\"name\":\"Arroz de forma\",\n" +            
"}]"

 //Convert jsonString to jsonArray

let json: AnyObject? = jsonString.parseJSONString
print("Parsed JSON: \(json!)")
print("json[2]: \(json![2])")

Wszystkie zasługi należą do pierwotnego użytkownika, właśnie zaktualizowałem do najnowszej szybkiej wersji

swiftBoy
źródło
7

Napisałem bibliotekę, dzięki której praca z danymi json i deserializacja w Swift to pestka. Możesz go pobrać tutaj: https://github.com/isair/JSONHelper

Edycja: zaktualizowałem moją bibliotekę, możesz to teraz zrobić za pomocą tego:

class Business: Deserializable {
    var id: Int?
    var name = "N/A"  // This one has a default value.

    required init(data: [String: AnyObject]) {
        id <-- data["id"]
        name <-- data["name"]
    }
}

var businesses: [Business]()

Alamofire.request(.GET, "http://MyWebService/").responseString { (request, response, string, error) in
    businesses <-- string
}

Stara odpowiedź:

Po pierwsze, zamiast używać .responseString, użyj .response, aby uzyskać obiekt odpowiedzi. Następnie zmień kod na:

func getAllBusinesses() {

    Alamofire.request(.GET, "http://MyWebService/").response { (request, response, data, error) in
        var businesses: [Business]?

        businesses <-- data

        if businesses == nil {
            // Data was not structured as expected and deserialization failed, do something.
        } else {
            // Do something with your businesses array. 
        }
    }
}

Musisz stworzyć taką klasę biznesową:

class Business: Deserializable {
    var id: Int?
    var name = "N/A"  // This one has a default value.

    required init(data: [String: AnyObject]) {
        id <-- data["id"]
        name <-- data["name"]
    }
}

Pełną dokumentację można znaleźć w moim repozytorium GitHub. Baw się dobrze!

isair
źródło
Dzięki! Ale bardziej szukałem niestandardowego sposobu, aby to zrobić, ponieważ jest to projekt uniwersytecki, nad którym pracuję, a mój przełożony nie będzie zbyt szczęśliwy, jeśli zobaczy, że używam API.
Hasan Nizamani
Wykorzystam Twoją bibliotekę w moich prywatnych projektach. Dzięki!!
Hasan Nizamani
Cieszę się, że mogłem pomóc w taki czy inny sposób. Powodzenia w twoich projektach! ^^
isair
6

Dla Swift 4 napisałem to rozszerzenie przy użyciu protokołu Codable :

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

extension String {

    func parse<D>(to type: D.Type) -> D? where D: Decodable {

        let data: Data = self.data(using: .utf8)!

        let decoder = JSONDecoder()

        do {
            let _object = try decoder.decode(type, from: data)
            return _object

        } catch {
            return nil
        }
    }
}

var jsonString = "[\n" +
    "{\n" +
    "\"id\":72,\n" +
    "\"name\":\"Batata Cremosa\",\n" +
    "},\n" +
    "{\n" +
    "\"id\":183,\n" +
    "\"name\":\"Caldeirada de Peixes\",\n" +
    "},\n" +
    "{\n" +
    "\"id\":76,\n" +
    "\"name\":\"Batata com Cebola e Ervas\",\n" +
    "},\n" +
    "{\n" +
    "\"id\":56,\n" +
    "\"name\":\"Arroz de forma\",\n" +
"}]"

let businesses = jsonString.parse(to: [Business].self)
Danny Narváez
źródło
4

Do iOS 10& Swift 3, używając Alamofire & Gloss :

Alamofire.request("http://localhost:8080/category/en").responseJSON { response in

if let data = response.data {

    if let categories = [Category].from(data: response.data) {

        self.categories = categories

        self.categoryCollectionView.reloadData()
    } else {

        print("Casting error")
    }
  } else {

    print("Data is null")
  }
}

a tutaj jest klasa Category

import Gloss

struct Category: Decodable {

    let categoryId: Int?
    let name: String?
    let image: String?

    init?(json: JSON) {
        self.categoryId = "categoryId" <~~ json
        self.name = "name" <~~ json
        self.image = "image" <~~ json
    }
}

IMO, to zdecydowanie najbardziej eleganckie rozwiązanie.

Ponadczasowy
źródło
2
let jsonString = "{\"id\":123,\"Name\":\"Munish\"}"

Konwertuj ciąg na NSData

 var data: NSData =jsonString.dataUsingEncoding(NSUTF8StringEncoding)!

 var error: NSError?

Konwertuj NSData na AnyObject

var jsonObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data,     options: NSJSONReadingOptions.allZeros, error: &error)

println("Error: \\(error)")

let id = (jsonObject as! NSDictionary)["id"] as! Int

let name = (jsonObject as! NSDictionary)["name"] as! String

println("Id: \\(id)")

println("Name: \\(name)")
Munish Kapoor
źródło
2

Podoba mi się odpowiedź RDC, ale po co ograniczać zwracany kod JSON, aby mieć tylko tablice na najwyższym poziomie? Musiałem zezwolić na słownik na najwyższym poziomie, więc zmodyfikowałem go w ten sposób:

extension String
{
    var parseJSONString: AnyObject?
    {
        let data = self.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)

        if let jsonData = data
        {
            // Will return an object or nil if JSON decoding fails
            do
            {
                let message = try NSJSONSerialization.JSONObjectWithData(jsonData, options:.MutableContainers)
                if let jsonResult = message as? NSMutableArray {
                    return jsonResult //Will return the json array output
                } else if let jsonResult = message as? NSMutableDictionary {
                    return jsonResult //Will return the json dictionary output
                } else {
                    return nil
                }
            }
            catch let error as NSError
            {
                print("An error occurred: \(error)")
                return nil
            }
        }
        else
        {
            // Lossless conversion of the string was not possible
            return nil
        }
    }
MR_22
źródło
2

SWIFT4 - Łatwy i elegancki sposób dekodowania ciągów JSON do Struct.

Pierwszy krok - zakoduj łańcuch do danych z kodowaniem .utf8.

Następnie dekoduj swoje dane do YourDataStruct.

struct YourDataStruct: Codable {

let type, id: String

init(_ json: String, using encoding: String.Encoding = .utf8) throws {
    guard let data = json.data(using: encoding) else {
        throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
    }
    try self.init(data: data)
}

init(data: Data) throws {
    self = try JSONDecoder().decode(YourDataStruct.self, from: data)
}                                                                      
}

do { let successResponse = try WSDeleteDialogsResponse(response) }
} catch {}
Vitya Shurapov
źródło
1

Możesz użyć swift.quicktype.io do konwersji JSONna albo structlubclass . Nawet ty możesz wspomnieć o wersji kodu swift to genrate.

Przykład JSON:

{
  "message": "Hello, World!"
}

Wygenerowany kod:

import Foundation

typealias Sample = OtherSample

struct OtherSample: Codable {
    let message: String
}

// Serialization extensions

extension OtherSample {
    static func from(json: String, using encoding: String.Encoding = .utf8) -> OtherSample? {
        guard let data = json.data(using: encoding) else { return nil }
        return OtherSample.from(data: data)
    }

    static func from(data: Data) -> OtherSample? {
        let decoder = JSONDecoder()
        return try? decoder.decode(OtherSample.self, from: data)
    }

    var jsonData: Data? {
        let encoder = JSONEncoder()
        return try? encoder.encode(self)
    }

    var jsonString: String? {
        guard let data = self.jsonData else { return nil }
        return String(data: data, encoding: .utf8)
    }
}

extension OtherSample {
    enum CodingKeys: String, CodingKey {
        case message
    }
}
Rugmangathan
źródło
1

Korzystając z biblioteki SwiftyJSON , możesz to zrobić

if let path : String = Bundle.main.path(forResource: "tiles", ofType: "json") {
    if let data = NSData(contentsOfFile: path) {
        let optData = try? JSON(data: data as Data)
        guard let json = optData else {
            return
        }
        for (_, object) in json {
            let name = object["name"].stringValue
            print(name)
        }
    }
} 
Mohamed Saleh
źródło
0

Oto próbka, dzięki której wszystko będzie prostsze i łatwiejsze. Moje dane String w mojej bazie danych to plik JSON, który wygląda następująco:

[{"stype":"noun","sdsc":"careless disregard for consequences","swds":"disregard, freedom, impulse, licentiousness, recklessness, spontaneity, thoughtlessness, uninhibitedness, unrestraint, wantonness, wildness","anwds":"restraint, self-restraint"},{"stype":"verb","sdsc":"leave behind, relinquish","swds":"abdicate, back out, bail out, bow out, chicken out, cop out, cut loose, desert, discard, discontinue, ditch, drop, drop out, duck, dump, dust, flake out, fly the coop, give up the ship, kiss goodbye, leave, leg it, let go, opt out, pull out, quit, run out on, screw, ship out, stop, storm out, surrender, take a powder, take a walk, throw over, vacate, walk out on, wash hands of, withdraw, yield","anwds":"adopt, advance, allow, assert, begin, cherish, come, continue, defend, favor, go, hold, keep, maintain, persevere, pursue, remain, retain, start, stay, support, uphold"},{"stype":"verb","sdsc":"leave in troubled state","swds":"back out, desert, disown, forsake, jilt, leave, leave behind, quit, reject, renounce, throw over, walk out on","anwds":"adopt, allow, approve, assert, cherish, come, continue, defend, favor, keep, pursue, retain, stay, support, uphold"}]

Aby załadować te dane ciągów JSON, wykonaj następujące proste kroki. Najpierw utwórz klasę dla mojego obiektu MoreData w następujący sposób:

class  MoreData {
public private(set) var stype : String
public private(set) var sdsc : String
public private(set) var swds : String
public private(set) var anwds : String

init( stype : String, sdsc : String, swds : String, anwds : String) {

    self.stype = stype
    self.sdsc = sdsc
    self.swds = swds
    self.anwds = anwds
}}

Po drugie, utwórz moje rozszerzenie String dla mojego ciągu JSON w następujący sposób:

extension  String {
func toJSON() -> Any? {
    guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil }
    return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
}}

Po trzecie, utwórz klasę My Srevices, aby obsłużyć moje dane ciągów w następujący sposób:

class Services {
static let instance: Services = Services()

func loadMoreDataByString(byString: String) -> [MoreData]{
    var  myVariable = [MoreData]()

    guard let ListOf = byString.toJSON() as? [[String: AnyObject]] else { return  [] }

    for object in ListOf {
        let stype  = object["stype"] as? String ?? ""
        let sdsc  = object["sdsc"] as? String ?? ""
         let swds  = object["swds"] as? String ?? ""
        let anwds  = object["anwds"] as? String ?? ""

        let myMoreData = MoreData(stype : stype, sdsc : sdsc, swds : swds, anwds : anwds)
        myVariable.append(myMoreData)
    }
    return myVariable
}}

Na koniec wywołaj tę funkcję z kontrolera widoku, aby załadować dane w widoku tabeli w następujący sposób:

    func handlingJsonStringData(){
    moreData.removeAll(keepingCapacity: false)
    moreData =  Services.instance.loadMoreDataByString(byString: jsonString)
    print(self.moreData.count)
    tableView.reloadData()
}
hardiBSalih
źródło
0

Może ktoś pomoże. Podobny przykład.

To jest nasza Codableklasa do wiązania danych. Możesz łatwo utworzyć tę klasę za pomocą SwiftyJsonAccelerator

 class ModelPushNotificationFilesFile: Codable {

  enum CodingKeys: String, CodingKey {
    case url
    case id
    case fileExtension = "file_extension"
    case name
  }

  var url: String?
  var id: Int?
  var fileExtension: String?
  var name: String?

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

  required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    url = try container.decodeIfPresent(String.self, forKey: .url)
    id = try container.decodeIfPresent(Int.self, forKey: .id)
    fileExtension = try container.decodeIfPresent(String.self, forKey: .fileExtension)
    name = try container.decodeIfPresent(String.self, forKey: .name)
  }

}

To jest Json String

    let jsonString = "[{\"name\":\"\",\"file_extension\":\"\",\"id\":10684,\"url\":\"https:\\/\\/homepages.cae.wisc.edu\\/~ece533\\/images\\/tulips.png\"},

{\"name\":\"\",\"file_extension\":\"\",\"id\":10684,\"url\":\"https:\\/\\/homepages.cae.wisc.edu\\/~ece533\\/images\\/arctichare.png\"},

{\"name\":\"\",\"file_extension\":\"\",\"id\":10684,\"url\":\"https:\\/\\/homepages.cae.wisc.edu\\/~ece533\\/images\\/serrano.png\"},

{\"name\":\"\",\"file_extension\":\"\",\"id\":10684,\"url\":\"https:\\/\\/homepages.cae.wisc.edu\\/~ece533\\/images\\/peppers.png\"},

{\"name\":\"\",\"file_extension\":\"\",\"id\":10684,\"url\":\"https:\\/\\/homepages.cae.wisc.edu\\/~ece533\\/images\\/pool.png\"}]"

Tutaj konwertujemy na szybki obiekt.

   let jsonData = Data(jsonString.utf8)

        let decoder = JSONDecoder()

        do {
            let fileArray = try decoder.decode([ModelPushNotificationFilesFile].self, from: jsonData)
            print(fileArray)
            print(fileArray[0].url)
        } catch {
            print(error.localizedDescription)
        }
Shourob Datta
źródło
0

Użyj swiftyJson swiftyJson

platform :ios, '8.0'
use_frameworks!

target 'MyApp' do
pod 'SwiftyJSON', '~> 4.0'
end

Stosowanie

import SwiftyJSON

let json = JSON(jsonObject)

let id = json["Id"].intValue
let name = json["Name"].stringValue
let lat = json["Latitude"].stringValue
let long = json["Longitude"].stringValue
let address = json["Address"].stringValue
            
print(id)
print(name)
print(lat)
print(long)
print(address)
Zmiana zespołu
źródło