[Swift] Json parse in array en deze aan tabel toevoegen

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • grote_oever
  • Registratie: Augustus 2002
  • Laatst online: 08:02
Mijn vraag
Na veel knutselen met Domoticz miste ik, naast Pilot, een app voor de iphone om mijn domoticz aan te sturen. Daarom ben ik de laatste paar weken aan de slag gegaan met xcode. Waarbij ik de eerste week, met mijn beperkte kennis aan swift, aardig aan het kloten was met alleen het opslaan van velden kom ik bij mijn, misschien op dit moment iets over mijn hoofd gaande probleem. Ik hoop dat jullie me hier een klein zetje in de richting kunnen geven.

Ik parse een json URL van mijn domoticz. Deze URL bouw ik op vanuit de velden die opgeslagen zijn vanuit mijn settings.

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 // declaratie variabelen
        
        let LaadIPadres = userDefaults.stringForKey("IPadresItem")
        let LaadPort = userDefaults.stringForKey("PortItem")
        let URLalldevices = "http://" + LaadIPadres! + ":" + LaadPort! + "/json.htm?type=devices&filter=light&used=true&order=Name"

// begin Json //

        let requestURL: NSURL = NSURL(string: URLalldevices)!
        let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
        let session = NSURLSession.sharedSession()
        let task = session.dataTaskWithRequest(urlRequest) {
            (data, response, error) -> Void in
            
            let httpResponse = response as! NSHTTPURLResponse
            let statusCode = httpResponse.statusCode
         
            if (statusCode == 200) {
                do{
                    
                    let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
                    let results: NSArray = json["result"] as! NSArray
                    for item in results {
                        let name: String = item["Name"] as! String
//                        let ID: String = item["Status"] as! String
//                        let Switch: String = item["SwitchType"] as! String
                        print("\(name)")

                        self.objects.addObject(name)
                    }
                }catch {
                    print("Er gaat iets goed fout")
                    
                }
                
            }
            
        }
        
        task.resume()
        // end
        self.tableview.reloadData()


Resultaat:

In mijn "console" zie ik dat print("\(name)") al mijn domoticz devices weergeeft. De parse lijkt dus goed te zijn gegaan. Wanneer ik onderaan, onder task.resume() de array wil aanroepen lijkt het mis te gaan. Ik kan de array niet meer weervinden. Ik heb dus het gevoel dat de array niet meer bestaat. Wanneer ik hier iets aanpas krijg ik een foutmelding: Terminated due to signal 11

Er zijn op het web veel tutorials voor het lezen van Json, maar vaak liep ik hier tegen problemen aan. Vooral omdat ik een remote URL gebruik en ik er vervolgens een tabel van wil maken. Het bovengenoemde script is een groot deel geplukt vanuit een aantal tutorials en het beetje zelfkennis wat ik heb. Gezien mijn kennis gaat dit probleem ver boven mijn hoofd, maar toch een belangrijke functionaliteit binnen de app die ik wil maken.

Iemand enig idee waar mijn denkfout zit?

[edit]

Oeh, vergeten te melden. De tabel is prima te vullen met de volgende code:

code:
1
2
3
self.objects.addObject("item 1")
self.objects.addObject("item 2")
self.objects.addObject("item 3")


Console geeft het volgende weer:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Meterkast
Kamer
Tv meubel
Deurbel
Cateye motiondetector
Hoofdschakelaar
Group d.Mancave
Hue woonkamer
Mancave spot 4
Mancave spot 3
Mancave spot 2
Mancave spot 1
Mancave lamp
Woonkamer hue 1
Woonkamer hue 2
Woonkamer hue 3
PowerOff
TV Kijken
MediaPC
FM Radio Luisteren

[ Voor 10% gewijzigd door grote_oever op 24-04-2016 22:39 ]

Beste antwoord (via grote_oever op 26-04-2016 07:09)


  • BoringDay
  • Registratie: Maart 2009
  • Laatst online: 13-05 21:49
"Terminated due to signal 11"
In dat geval voer ik meestal eerst een clean uit (menu:product->clean)

Verder is het een kwestie van debuggen:
Alles wat binnen de block van session.dataTaskWithRequest valt wordt pas uitgevoerd wanneer je task.resume() uitvoert. Op dat moment krijg je de data binnen. Met print(data)/print(json) kan je dus precies zien wat je aan data binnen krijgt en daarmee vrij eenvoudig op te lossen, lijkt mij?

Nog een puntje:
self.tableview.reloadData() staat volgens mij daar ook niet goed.
Je wil namelijk pas je data herladen op het moment je de resultaten binnen hebt.
Die regel zou ik dan onder: self.objects.addObject(name) plaatsen als volgt:

Swift:
1
2
3
dispatch_async(dispatch_get_main_queue()) { () -> Void in
            self.tableView.reloadData()
}

[ Voor 34% gewijzigd door BoringDay op 25-04-2016 06:25 ]

Alle reacties


Acties:
  • Beste antwoord
  • +1 Henk 'm!

  • BoringDay
  • Registratie: Maart 2009
  • Laatst online: 13-05 21:49
"Terminated due to signal 11"
In dat geval voer ik meestal eerst een clean uit (menu:product->clean)

Verder is het een kwestie van debuggen:
Alles wat binnen de block van session.dataTaskWithRequest valt wordt pas uitgevoerd wanneer je task.resume() uitvoert. Op dat moment krijg je de data binnen. Met print(data)/print(json) kan je dus precies zien wat je aan data binnen krijgt en daarmee vrij eenvoudig op te lossen, lijkt mij?

Nog een puntje:
self.tableview.reloadData() staat volgens mij daar ook niet goed.
Je wil namelijk pas je data herladen op het moment je de resultaten binnen hebt.
Die regel zou ik dan onder: self.objects.addObject(name) plaatsen als volgt:

Swift:
1
2
3
dispatch_async(dispatch_get_main_queue()) { () -> Void in
            self.tableView.reloadData()
}

[ Voor 34% gewijzigd door BoringDay op 25-04-2016 06:25 ]


Acties:
  • 0 Henk 'm!

  • grote_oever
  • Registratie: Augustus 2002
  • Laatst online: 08:02
Bedankt voor de tip, ik zal vanavond gelijk kijken. Ik had na mijn mening al de code onder tast.resume() gezet, maar ga het nogmaals proberen.

Klopt het wel dat ik een 2d array heb gemaakt in bovenstaande voorbeeld? Normaal zou ik de array zo uitgelezen hebben, maar volgens mij moet ik dan nu een geneste array maken.

Eerste veld:
code:
1
2
3
for item in results {
    print(item)
}


Tweede veld:

code:
1
2
3
4
5
for item1 in results {
    for item2 in results {
    print(item2)
} 
}

Acties:
  • 0 Henk 'm!

  • BoringDay
  • Registratie: Maart 2009
  • Laatst online: 13-05 21:49
Als je even een print(json) doet dan zie je exact hoe je ontvangen data is opgebouwd.
Dat is dus ook wat je eerst in kaart moet brengen voor dat je het key/value's wilt gaan uitlezen.

[ Voor 6% gewijzigd door BoringDay op 25-04-2016 12:52 ]


Acties:
  • +1 Henk 'm!

  • Marco1994
  • Registratie: Juli 2012
  • Laatst online: 11-10 20:09
Dit is even een tijdje gelezen, heb voor een schoolproject iets vergelijkbaars gedaan.

RestApiManager
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import Foundation

typealias ServiceResponse = (JSON, NSError?) -> Void

class RestApiManager: NSObject {
    static let sharedInstance = RestApiManager()
    
    let baseURL = "http://urlHier.nl"
    
    func getJson(onCompletion: (JSON) -> Void) {
        let route = baseURL
        makeHTTPGetRequest(route, onCompletion: { json, err in
            onCompletion(json as JSON)
        })
    }
    
    fun getSomething(onCompletion: (JSON) -> Void) {
        let route = baseURL
        makeHTTPGetRequest(route, onCompletion: { json, err in
            onCompletion(json as JSON)
        })
    }
    
    func makeHTTPGetRequest(path: String, onCompletion: ServiceResponse) {
        let request = NSMutableURLRequest(URL: NSURL(string: path)!)
        
        let session = NSURLSession.sharedSession()
        
        let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
            let json:JSON = JSON(data: data!)
            onCompletion(json, error)
        })
        task.resume()
    }
}


call implementation
code:
1
2
3
4
5
6
7
8
9
10
11
private func getSomething(){
            RestApiManager.sharedInstance.getSomething { json in
                for (_, subJson): (String, JSON) in json {
                    let jsonValue:JSON = JSON(subJson.object)
                    let name:String
                    name = jsonValue:JSON["Name"].string!
                    print(name)
                    self.values.append(name)
                }
            }
    }


hierboven een gestripte versie hoe ik het geïmplementeerd heb, wellicht heb je hier iets aan.

Acties:
  • 0 Henk 'm!

  • BoringDay
  • Registratie: Maart 2009
  • Laatst online: 13-05 21:49
De http request is zijn probleem niet, het gaat om de binnenkomende data (JSON object) die vertaald moet worden. Dat is typisch iets waarvoor je documentatie nodig hebt en door te debuggen (inhoud bestuderen).

Acties:
  • 0 Henk 'm!

  • Marco1994
  • Registratie: Juli 2012
  • Laatst online: 11-10 20:09
In dit geval kan hij gewoon het joon object printen om te kijken welke data hij nodig heeft. Moet het probleem niet zijn.

Acties:
  • 0 Henk 'm!

  • grote_oever
  • Registratie: Augustus 2002
  • Laatst online: 08:02
Marco1994 schreef op maandag 25 april 2016 @ 14:31:
In dit geval kan hij gewoon het joon object printen om te kijken welke data hij nodig heeft. Moet het probleem niet zijn.
Ik zal vanavond even aangeven of het me gelukt is. Misschien is het inderdaad het probleem niet, maar de leercurve van swift is best stijl :) Ik zag het gisteren namelijk niet meer.

In ieder geval bedankt voor het meedenken. Mocht het lukken dan markeer ik het beste antwoord.

[ Voor 5% gewijzigd door grote_oever op 25-04-2016 14:41 ]


Acties:
  • 0 Henk 'm!

  • grote_oever
  • Registratie: Augustus 2002
  • Laatst online: 08:02
Met deze toevoeging

code:
1
2
3
dispatch_async(dispatch_get_main_queue()) { () -> Void in
            self.tableView.reloadData()
}


krijg ik inderdaad al mijn devices in een tabel te zien. Wat me echter nog niet lukt is een array maken van de waardes, want naast de naam wil ik ook de array uitbreiden met het ID en de status van de lamp. Op één of andere manier lukt me dit nog niet lekker.

[ Voor 90% gewijzigd door grote_oever op 25-04-2016 21:42 ]


Acties:
  • +1 Henk 'm!

  • Harm
  • Registratie: Mei 2002
  • Niet online
grote_oever schreef op maandag 25 april 2016 @ 21:13:
Met deze toevoeging

code:
1
2
3
dispatch_async(dispatch_get_main_queue()) { () -> Void in
            self.tableView.reloadData()
}


krijg ik inderdaad al mijn devices in een tabel te zien. Wat me echter nog niet lukt is een array maken van de waardes, want naast de naam wil ik ook de array uitbreiden met het ID en de status van de lamp. Op één of andere manier lukt me dit nog niet lekker.
Als je die array nou eens buiten de task-code aanmaakt (var results) en in de task-code daarheen schrijft? Ik meen me te herinneren dat het zo moet.

Acties:
  • +1 Henk 'm!

  • BoringDay
  • Registratie: Maart 2009
  • Laatst online: 13-05 21:49
grote_oever schreef op maandag 25 april 2016 @ 21:13:
Met deze toevoeging

code:
1
2
3
dispatch_async(dispatch_get_main_queue()) { () -> Void in
            self.tableView.reloadData()
}


krijg ik inderdaad al mijn devices in een tabel te zien. Wat me echter nog niet lukt is een array maken van de waardes, want naast de naam wil ik ook de array uitbreiden met het ID en de status van de lamp. Op één of andere manier lukt me dit nog niet lekker.
Je kan een collectie bijhouden van apparaat informatie bijvoorbeeld door:
- DeviceInfo: model die informatie voor een enkele apparaat bijhoud
- DeviceList: collectie van DeviceInfo

Iets in een vorm als:

Swift:
1
2
3
4
class DeviceInfo {
    private var name:String?
    private var version:String?
}


Swift:
1
2
3
class DeviceList {
    private static var devicelist:[DeviceInfo] = []
}


De JSON data is echter dusdanig specifiek dat je die zelf moet uitzoeken hoe het in elkaar zit.
(wellicht een array met dictionaries waarin key/value waardes staan)

Acties:
  • 0 Henk 'm!

  • grote_oever
  • Registratie: Augustus 2002
  • Laatst online: 08:02
Het is me inmiddels gelukt dankzij jullie advies. Enige waar ik nog moeite mee heb is het volgende:

Ik heb dus een werkende class:

code:
1
2
3
4
5
6
7
8
9
10
11
12
class JSONresult{

    var idx: String
    var Naam: String
    var Status: String

    init(idx: String, Naam: String, Status: String){
        self.idx = idx
        self.Naam = Naam
        self.Status = Status
    }
}


Deze class maak ik dus een array van met de naam Jsonarray. Vervolgens voeg ik deze array toe aan een array met de naam objects

code:
1
2
3
4
5
6
7
          for item in results {
                    let name: String = item["Name"] as! String
                    let ID: String = item["idx"] as! String
                    let status: String = item["Status"] as! String
                    let Jsonarray = JSONresult(idx: ID, Naam: name, Status: status)
                    self.objects.addObject(Jsonarray)
}


Tot zover lijkt alles goed te gaan, maar het uitlezen van de array objects lukt me niet. Elke keer als ik het uit probeer te lezen krijg ik of een foutmelding of de volgende output:

code:
1
2
3
4
mijnDomoticz.JSONresult
mijnDomoticz.JSONresult
mijnDomoticz.JSONresult
mijnDomoticz.JSONresult


Ik probeer de array op de volgende manier te printen:

code:
1
2
3
    print(self.objects.objectAtIndex(indexPath.row).objectForKey("ID"))
    print(self.objects.id.objectAtIndex(indexPath.row))
    print(self.objects.objectAtIndex(indexPath.row))


De eerste twee regels geven me een foutmelding. De laatste regel geeft de output die ik hierboven heb gepost, namelijk mijnDomoticz.JSONresult. Ik heb ik ipv addobject addObjectsfromArray geprobeerd, maar dat lukt ook niet. het printen van Jsonarray.ID of Jsonarray.Naam werkt perfect. De meeste tutorials gaan niet zo diep in op arrays. Iemand enig idee waar de denkfout zit? Persoonlijk denk ik dat de fout toch zit in de Jsonarray.

Acties:
  • +2 Henk 'm!

  • BoringDay
  • Registratie: Maart 2009
  • Laatst online: 13-05 21:49
Tip: Noem de class geen JSONResult maar DeviceInfo, dat is volgens mij de informatie die je wil verzamelen?

Ik heb geen idee wat dit is maar ik vermoed dat dit je lijst met apparaat info moet voorstellen?
Swift:
1
self.objects.addObject(Jsonarray)


Dit lijkt me een stuk leesbaarder (ook voor andere)?
Swift:
1
self.devicelist.addObject(deviceInfo)


Is Jsonarray hier een array? volgens mij niet
Swift:
1
let Jsonarray = JSONresult(idx: ID, Naam: name, Status: status)

Dit lijkt me wat netter als je het wat opschoont
Schrijf index gewoon voluit i.p.v. afkortingen als IDX.
Hou 1 taal i.p.v. mix van Nederlands en Engels.
Swift:
1
let deviceInfo = DeviceInfo(index:ID, Name:name, Status: status)


Het lijkt me verstandig dat je eerst aan je code structuur geeft en overzicht krijgt.
Wat je hier allemaal uit probeert vind ik wel heel artistiek :-)
Swift:
1
2
3
print(self.objects.objectAtIndex(indexPath.row).objectForKey("ID"))
print(self.objects.id.objectAtIndex(indexPath.row))
print(self.objects.objectAtIndex(indexPath.row))


Als je een lijst hebt met objecten, stel:
- deviceList: collectie van deviceInfo
Zou je dan niet iets in de trend van deviceInfo = deviceList[indexPath.row] kunnen doen? (immers wil je de deviceInfo hebben die je wil weergeven in de tableView).

Mijn advies is:
- code opschonen
- handhaaf correcte benaming zodat je het makkelijk kan lezen
- verdiep je nog even in de stof van Array's en TableView
- zorg dat je begrijpt wat er gebeurd, anders wordt het een rommeltje en loop je vast op dit soort fouten.

Hier nog een handige pagina voor naslag werk:
https://developer.apple.c...uage/CollectionTypes.html

Lijkt wat netter?

Swift:
1
2
3
4
5
6
7
8
9
10
11
12
class DeviceInfo {

    private (set) var index: String
    private (set) var name: String
    private (set) var status: String

    init(index: String, name: String, status: String){
        self.index = index
        self.name = name
        self.status = status
    }
}


DeviceList zou je ongeveer op deze manier kunnen opstellen

Swift:
1
2
3
4
5
6
7
8
9
10
11
12
class DeviceList {
         private static var devicelist:[DeviceInfo] = []

         static func addDeviceInfo(deviceInfo:DeviceInfo) {
             devicelist.append(deviceInfo)
         }

        static func deviceInfoAt(index:Int)->DeviceInfo {
           // hier ga je wel uitkomen? return devicelist[index];
       }

}

[ Voor 20% gewijzigd door BoringDay op 29-04-2016 00:13 ]


Acties:
  • 0 Henk 'm!

  • grote_oever
  • Registratie: Augustus 2002
  • Laatst online: 08:02
BoringDay schreef op donderdag 28 april 2016 @ 23:58:
Tip: Noem de class geen JSONResult maar DeviceInfo, dat is volgens mij de informatie die je wil verzamelen?
Heel erg bedankt voor je hulp. Je post zijn namelijk heel duidelijk en behulpzaam. Ik zal zeker eerste naar je laatste advies kijken om de code op te schonen. Mijn laatste vraag ging voornamelijk al over het opschonen, gezien ik graag 1 variabele wil hebben waar al mijn device info in zit. Op dit moment gebruik ik quick and dirty meerdere NSmutablearray's, maar daar krijg ik later in mijn app gewoon problemen mee, gezien ik daarin niet kan filteren (b.v. alle favoriete apparaten).

Mocht je het interessant vinden om te weten. De app haalt informatie op vanaf mijn domoticz via JSON:

Afbeeldingslocatie: http://i.imgur.com/sgpwx3O.png

De app werkt tot nu toe, met de beperkte functie's, behoorlijk goed. Met de knop aan (op dit moment een button, maar zal een switch worden) kan ik de lampen thuis aanzetten :)

Zo bevat de JSON van domoticz ongeveer 40 parameters. De code waar ik nu mee aan het stoeien ben is puur mijn leercurve en het enige wat hij nu doet is een tabel met inhoud van mijn domoticz tonen. Met de knop aan kan ik de lampen aanzetten. In de settings worden de settings opgeslagen om de JSON op te kunnen halen. Mijn fout is inderdaad het verkeerde naamgebruiken en verkeerd hoofdlettergebruik.

Ik ga je advies dit weekend even verder uitwerken. In ieder geval bedankt.

JSON:
{
"ActTime" : 1461959417,
"ServerTime" : "2016-04-29 21:50:17",
"Sunrise" : "06:07",
"Sunset" : "20:56",
"result" : [
{
"AddjMulti" : 1.0,
"AddjMulti2" : 1.0,
"AddjValue" : 0.0,
"AddjValue2" : 0.0,
"BatteryLevel" : 255,
"CustomImage" : 9,
"Data" : "On",
"Description" : "",
"Favorite" : 0,
"HardwareID" : 2,
"HardwareName" : "RFXCOM",
"HardwareType" : "RFXCOM - RFXtrx433 USB 433.92MHz Transceiver",
"HardwareTypeVal" : 1,
"HaveDimmer" : true,
"HaveGroupCmd" : true,
"HaveTimeout" : false,
"ID" : "3080506",
"Image" : "Generic",
"IsSubDevice" : false,
"LastUpdate" : "2016-01-16 13:01:38",
"Level" : 0,
"LevelInt" : 0,
"MaxDimLevel" : 15,
"Name" : "Meterkast",
"Notifications" : "false",
"PlanID" : "2",
"PlanIDs" : [ 2 ],
"Protected" : false,
"ShowNotifications" : true,
"SignalLevel" : "-",
"Status" : "On",
"StrParam1" : "",
"StrParam2" : "",
"SubType" : "HomeEasy EU",
"SwitchType" : "On/Off",
"SwitchTypeVal" : 0,
"Timers" : "false",
"Type" : "Lighting 2",
"TypeImg" : "lightbulb",
"Unit" : 1,
"Used" : 1,
"UsedByCamera" : false,
"XOffset" : "0",
"YOffset" : "0",
"idx" : "12"
},
{
"AddjMulti" : 1.0,
"AddjMulti2" : 1.0,
"AddjValue" : 0.0,
"AddjValue2" : 0.0,
"BatteryLevel" : 255,
"CustomImage" : 0,
"Data" : "On",
"Description" : "",
"Favorite" : 1,
"HardwareID" : 2,
"HardwareName" : "RFXCOM",
"HardwareType" : "RFXCOM - RFXtrx433 USB 433.92MHz Transceiver",
"HardwareTypeVal" : 1,
"HaveDimmer" : true,
"HaveGroupCmd" : true,
"HaveTimeout" : false,
"ID" : "2030303",
"Image" : "Light",
"IsSubDevice" : false,
"LastUpdate" : "2016-04-29 21:12:11",
"Level" : 93,
"LevelInt" : 14,
"MaxDimLevel" : 15,
"Name" : "Kamer",
"Notifications" : "false",
"PlanID" : "2",
"PlanIDs" : [ 2 ],
"Protected" : false,
"ShowNotifications" : true,
"SignalLevel" : "-",
"Status" : "On",
"StrParam1" : "",
"StrParam2" : "",
"SubType" : "HomeEasy EU",
"SwitchType" : "On/Off",
"SwitchTypeVal" : 0,
"Timers" : "false",
"Type" : "Lighting 2",
"TypeImg" : "lightbulb",
"Unit" : 1,
"Used" : 1,
"UsedByCamera" : false,
"XOffset" : "0",
"YOffset" : "0",
"idx" : "39"
},
etc..
etc..
Pagina: 1