2012-08-28 18 views
7

Sto cercando di analizzare alcuni JSON restituiti da un servizio Web REST. Il ritorno dalla chiamata get() è un TStringStream. Sto usando dbxjson per lavorare con i dati. Per rendere le cose più facili da dimostrare qui, ho creato un progetto di test che riproduce l'errore senza chiamare il servizio Web (utilizza invece un file di testo per l'output del servizio Web). Ecco il codice:JSON empty array

var SL : TStringStream; 
    LJsonObj : TJSONObject; 
begin 
    SL := TStringStream.Create; 
    try 
    SL.LoadFromFile('output.txt'); 
    LJsonObj := TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(SL.DataString), 0) as TJSONObject; 
    finally 
    SL.Free; 
    end; 
end; 

A volte l'array phone_numbers in questo dato JSON è vuoto. Nell'oggetto flusso proveniente dalla chiamata di servizio web, sembra che questo:

{ 
    "Contact Information Service": { 
     "response": { 
      "phone_numbers": [ 

] 
     } 
    } 
} 

Questo fa sì che ParseJSONValue per restituire un valore pari a zero.

Tuttavia, se cambio la numeri_di_telefono array vuoto a questo nel mio test file txt:

{ 
    "Contact Information Service": { 
     "response": { 
      "phone_numbers": [] 
     } 
    } 
} 

funziona bene (cioè restituisce un TJSONObject). La differenza è lo spazio bianco nell'array vuoto. Per qualche motivo, la prima risposta JSON con spazi bianchi nell'array vuoto fa sì che ParseJSONValue restituisca zero. Funziona bene senza spazi bianchi tra le parentesi quadre.

Cosa sto facendo male con il mio parsing JSON? C'è una sorta di pre-analisi che devo fare prima di chiamare ParseJSONValue?

+1

Sembra che molto probabilmente si tratta di un bug nell'implementazione di TJSONByteReader, ma sinceramente il tentativo di dare un senso al codice di analisi rende impossibile dirlo a colpo d'occhio. Empiricamente le prove sono abbastanza chiare. Fortunatamente il mio lettore TJSONObject gestisce perfettamente questi casi. Tempo di pubblicare forse? :) – Deltics

+1

@Deltics: Davvero? L'ho tracciato mentre indagavo su questa domanda, e non ho trovato così difficile capire il codice di analisi.Penso che il parser sia scritto male - questo problema sarebbe stato evitato completamente se avesse un lexer adatto invece di mescolare il lexing con l'analisi - ma non è troppo difficile capire cosa sta succedendo ... –

+0

Se hai trovato PeekByte() facile da capire, allora devi sognare in esadecimale. :) Quando pubblico il mio codice JSON vedrai la differenza tra il codice che considero leggibile (oserei dire manutenibile) e, um, dbxJSON. – Deltics

risposta

8

Questo problema non è esclusivo dell'implementazione Delphi JSON (DBXJSON), ho lavorato con alcuni parser JSON PHP con la stessa limitazione.

Ora, perché tutti gli spazi vuoti fuori un doppio stringhe tra virgolette letterali sono (e devono essere) ignorati dal parser JSON, è possibile rimuovere questi spazi bianchi in modo sicuro, quindi una possibile soluzione è Minify la stringa JSON, prima di per analizzarlo

Provate questo esempio, che usa espressioni regolari per rimuovere gli spazi bianchi extra da una stringa.

{$APPTYPE CONSOLE} 

{$R *.res} 


uses 
    System.RegularExpressions, 
    System.Classes, 
    System.SysUtils, 
    Data.DBXJSON; 

const 
JsonString= 
'{'+ 
' "Contact Information Service": {'+ 
'  "response": {'+ 
'   "phone_numbers": [  ]'+ 
'  }'+ 
' }'+ 
'}'; 

function JsonMinify(const S: string): string; 
begin 
Result:=TRegEx.Replace(S,'("(?:[^"\\]|\\.)*")|\s+', '$1'); 
end; 

procedure TestJSon; 
var 
    s : string; 
    SL : TStringStream; 
    LJsonObj : TJSONObject; 
begin 
    SL := TStringStream.Create; 
    try 
    s:=JsonMinify(JsonString); 
    SL.WriteString(s); 
    LJsonObj := TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(SL.DataString), 0) as TJSONObject; 
    Writeln(LJsonObj.Size); 
    finally 
    SL.Free; 
    end; 
end; 

begin 
try 
    TestJSon; 
except 
    on E:Exception do 
     Writeln(E.Classname, ':', E.Message); 
end; 
Writeln('Press Enter to exit'); 
Readln; 
end. 
7

Dai un'occhiata allo TJsonObject.ParseArray. Troverete questo:

while ValueExpected or (Br.PeekByte <> Ord(']')) do 
begin 
    ConsumeWhitespaces(Br); 
    Pos := ParseValue(Br, JsonArray); 
    if Pos <= 0 then 
    Exit(Pos); 

Così in alto della matrice (immediatamente dopo che legge la parentesi aperta), se il carattere successivo non è una parentesi chiusa, mangia spazi e poi provo a leggere un JSON valida valore. Una parentesi chiusa non è un valore JSON valido, quindi viene visualizzato a questo punto.

Questo sembra essere JSON valido, (posso convincere il mio browser ad accettarlo come oggetto JavaScript valido), quindi questo dovrebbe essere considerato un bug nella libreria DBXJSON. Potrebbe essere necessario eseguire una pre-analisi, utilizzare una libreria JSON diversa (ce ne sono una manciata per Delphi) o trovare un modo per garantire che le informazioni che ti vengono inviate non contengano questo modello.

In entrambi i casi, si dovrebbe segnalare questo a QC come un bug.

+1

Ma non lo si riparerà in XE2 anche se è un errore chiaro e semplice implementare correttamente le specifiche JSON - una funzionalità già pagata in XE2. Hai dimenticato di menzionare quella parte. – Deltics