2012-07-16 19 views
8

Buonasera ragazzi!Parsing JSON in TListBox

Attualmente sto cercando di mettere insieme un client CloudFlare per il desktop. Mi sono connesso alla loro API e ho recuperato con successo i risultati JSON con una richiesta POST (i cui risultati sono stati convertiti in un TMemo). Ora sto cercando di analizzare questi risultati in un TListBox (vedi area in grassetto per esempio). Il progetto è stato progettato in Firemonkey.

Ecco il layout formattato della risposta con alcuni contenuti di esempio;

{ 
- response: { 
    |- ips: [ 
     |- { 
     ip: "xxx.xxx.xxx.xxx", 
     classification: "threat", 
     hits: xx, 
     latitude: null, 
     longitude: null, 
     zone_name: "domain-example1" 
     }, 
     - { 
     ip: "yyy.yyy.yyy.yyy", 
     classification: "robot", 
     hits: yy, 
     latitude: null, 
     longitude: null, 
     zone_name: "domain-example2" 
     } 
     ] 
    } 
    result : "success", 
    msg: null 
} 

Ho provato diversi componenti diversi - SuperObject, Paweł Głowacki's JSON Designtime Parser, Tiny-JSON, LKJSON e il costruito nel DBXJSON. Tuttavia, non ho alcuna esperienza con JSON e non riesco a trovare il più semplice degli esempi da cui iniziare. Molti di loro mostrano dati di esempio, ma tutti quelli che ho provato non sembrano funzionare come mi aspetterei, molto probabilmente perché li sto fraintendendo. Presumo che i componenti funzionino, quindi ho bisogno di una guida per iniziare.

Ci sono centinaia, spesso migliaia di risultati nell'array "ips" (mi scuso se non è corretto, presumo sia noto come array ma, ancora una volta, sono completamente nuovo per JSON).

Quello che sto davvero cercando è una sorta di codice di esempio estremamente semplice che posso compilare da (insieme a quale componente utilizza per l'analisi e così via).

Per esempio, se volevo afferrare ogni ip dai risultati JSON, e mettere ognuno in una voce distinta in un (TListBox.add metodo che utilizza) TListBox, come potrei fare per raggiungere questo obiettivo?

Quando dico ip, intendo il valore (nel layout formattato sopra, questo sarebbe xxx.xxx.xxx.xxx o yyy.yyy.yyy.yyy).

Inoltre, se volessi trovare un "record" (?) Dal suo IP dai risultati JSON e inviare i dati a un array delphi - ad es.

Result : Array of String = ['"xxx.xxx.xxx.xxx"','"threat"','xx','null','null','"domain-example1"']; 

è possibile con JSON? (Se questa è vista come una domanda separata o non correlata, non esitare a modificarla piuttosto che chiudere la domanda nel suo complesso).

Il più vicino ho avuto modo di questo ha avuto non solo gli ip di, ma ogni altro pezzo di dati in un separato TListItem (cioè response, ips, ip, classification, xxx.xxx.xxx.xxx e tutto il resto aveva il suo oggetto, insieme a diversi elementi vuoti tra ogni oggetto non vuoto).

Sono sicuro che è estremamente semplice da fare, ma ci sono così tante informazioni su JSON che è un po 'travolgente per le persone nuove nel formato.

I migliori saluti, Scott Pritchard.

risposta

8

JSON è molto semplice e facile da capire, una volta compresi i concetti di base. Dai uno sguardo a http://json.org, dove spiega le cose.

Ci sono 4 concetti di base in JSON:

Un valore è qualsiasi elemento JSON: una stringa o un numero di base, una matrice o un oggetto. (. Altro che coppia)

Un matrice dovrebbe essere un concetto familiare: un elenco ordinato di valori. La principale differenza dagli array di Delphi è che gli array JSON non hanno un tipo definito per gli elementi; sono semplicemente "una serie di valori JSON".

A coppia è una coppia valore-chiave. La chiave può essere una stringa o un numero e il valore può essere qualsiasi valore JSON.

Un oggetto è una mappa associativa di coppie JSON. Puoi pensare concettualmente come TDictionary<string, JSON value>.

Quindi, se ho voluto prendere una matrice JSON dei dati del genere, e metterlo in un TListBox, mi piacerebbe fare qualcosa di simile (ad esempio DBXJSON, attenzione: non testato):

procedure TMyForm.LoadListBox(response: TJSONObject); 
var 
    i: integer; 
    ips: TJSONArray; 
    ip: TJSONObject; 
    pair: TJSONPair; 
begin 
    ListBox.Clear; 
    pair := response.Get('ips'); 
    if pair = nil then 
    Exit; 
    ips := pair.value as TJSONArray; 
    for i := 0 to ips.size - 1 do 
    begin 
    ip := ips.Get(i) as TJSONObject; 
    pair := ip.Get('ip'); 
    if pair = nil then 
     ListBox.AddItem('???', ip.Clone) 
    else ListBox.AddItem(pair.JsonString, ip.Clone); 
    end; 
end; 

Poi hai un elenco di indirizzi IP e oggetti associati contenenti il ​​record completo che puoi ottenere se l'utente ne seleziona uno. (Se si desidera inserire l'intero contenuto di ogni record nel controllo elenco, dare un'occhiata a TListView. Funziona meglio di TListBox per quello.)

E se si desidera creare un array di stringhe contenenti tutti i valori , fare qualcosa di simile:

function JsonObjToStringArray(obj: TJsonObject): TArray<string>; 
var 
    i: integer; 
begin 
    SetLength(result, obj.Size); 
    for i := 0 to obj.Size - 1 do 
    result[i] := obj.Get(i).JsonValue.ToString; 
end; 

questo è tutto il codice di esempio solo, ovviamente, ma dovrebbe darvi qualcosa su cui costruire.

+0

Molte grazie Mason! La risposta è ottima e ora capisco i concetti. Sono un po 'confuso, anche se forse non mi sono spiegato abbastanza nella prima domanda (l'ho detto in apertura, ma è facile dimenticarlo alla fine della domanda). Devo dire che ho il JSON in chiaro (cioè una stringa non analizzata come recuperata tramite l'API) in un 'TMemo' e come tale non è in grado di capire come posizionare il' lines.text' di Risposta 'JSONObject' richiesta dalla procedura. Inoltre, 'ips: = pair.value come TJSONArray' restituisce' E2015 - Operatore non applicabile'. –

+1

@scott: dai un'occhiata a "TJSONObject.Parse". E quella linea probabilmente dovrebbe usare 'pair.JsonValue' invece. Colpa mia. –

+0

Per riferimento futuro (e chiunque altro lo cerchi) è necessario includere l'unità 'System.JSON' nella clausola uses per fare in modo che l'esempio funzioni –

1

EDIT2: AV Risolto con estrema facilità.

MODIFICA: Dopo aver esaminato ulteriormente il mio codice, ho realizzato che causerebbe una quantità enorme di perdite di memoria. Tuttavia, da allora sono passato a SuperObject e ho trovato che lo stesso risultato può essere ottenuto in 2 righe di codice con solo 2 variabili e nessuna perdita di memoria;

Procedure ParseIPs; 
    ISO : ISuperObject; 
    MyItem : ISuperObject; 
begin 
    ISO := SO(RetrievedJSON); 
    for MyItem in ISO['response.ips'] do Memo2.Lines.Add(MyItem.S['ip']); 
end; 

RetrievedJSON è semplicemente un string contenente la unparsed, JSON in chiaro (cioè non un JSONString ma una stringa effettiva).

Ho lasciato il codice originale sotto per motivi di continuità.


Con l'aiuto di Mason Wheeler in una precedente risposta, così come una risposta fornita da "Teran" su question 9608794, ho costruito con successo il seguente per analizzare fino al livello attuale (vale a direla "matrice" contenente i dati) avevo bisogno di accedere, e quindi produrre tutti gli articoli con uno specifico JSONString.Value in una listbox (denominata LB1 nell'esempio seguente);

Procedure ParseIP; 
var 
    o, Jso, OriginalObject : TJSONObject; 
    ThePair, JsPair : TJSONPair; 
    TheVal, jsv : TJSONValue; 
    jsArr : TJsonArray; 
    StrL1 : String; 
    i, num : Integer; 
begin 
    num := 0; 
    o := TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(Memo1.Text), 0) as TJSONObject; 
    ThePair := o.Get('response'); 
    TheVal := ThePair.JsonValue; 
    STRL1 := TheVal.ToString; 
    JSV := TJSONObject.ParseJSONValue(STRL1); 
    OriginalObject := JSV as TJSONObject; 
    JSPair := OriginalObject.Get('ips'); 
    JSARR := JSPair.JsonValue as TJSONArray; 
    for i := 0 to JsArr.Size-1 do 
    begin 
     JSO := JSArr.Get(i) as TJSONObject; 
     for JSPAIR in JSO do 
     begin 
     num := num+1; 
      if JSPAIR.JsonString.Value = 'ip' then 
      begin 
      LB1.Items.Add(JSPair.JsonValue.Value); 
      end 
      else null; 
     end; 
    end; 
    ShowMessage('Items in listbox: ' + IntToStr(LB1.Items.Count)); 
    ShowMessage('Items in JSON: ' + IntToStr(num div JSO.Size)); 
    Jsv.Free; 
end; 

Anche se questo è un modo estremamente rotonda di farlo, mi permette di guardare ogni singolo passo, e vedere dove è l'iterazione giù attraverso il JSON e con estrema facilità, e di trasformarla in un funzione in cui posso produrre qualsiasi pezzo o intervallo di dati in base a uno dei criteri multipli. Per motivi di verifica ho ottenuto il numero corretto di elementi, ho aggiunto 2 routine ShowMessage alla fine; Uno per gli articoli nella listbox e uno per il numero di istanze di dati "ip" che stavo analizzando.

Questo codice è stato specificamente testato in FireMonkey con CloudFlare API JSON risultati che erano in uscita un TMemo esattamente come sono stati recuperati (una chiamata &calls_left&a=zone_ips&class=t&geo=1 API, ovviamente con il zone, token e email allegato in aggiunta). Dovrebbe essere relativamente facile modificarlo per funzionare con altri risultati dalle numerose altre chiamate API.

Per chiarire, ho provato il codice di Mason, ma sfortunatamente non riuscivo a farlo funzionare. Tuttavia, per il momento ho accettato la sua risposta sulla base del fatto che la spiegazione che dava sulle basi era degna di questo e mi ha aiutato ad arrivare a una soluzione finale e a inventare qualcosa da cui costruire e insegnarmi.