2012-04-07 6 views
1

Ich möchte JSON-ähnliche Strings parsen. Ihr einziger Unterschied zu normalem JSON ist das Vorhandensein von zusammenhängenden Kommas in Arrays. Wenn zwei solche Kommas vorhanden sind, bedeutet dies implizit, dass null dazwischen eingefügt werden soll. Beispiel:Python: JSON-artige JavaScript-Datenstrukturen analysieren (mit aufeinanderfolgenden Kommas)

 JSON-like: ["foo",,,"bar",[1,,3,4]] 
     Javascript: ["foo",null,null,"bar",[1,null,3,4]] 
Decoded (Python): ["foo", None, None, "bar", [1, None, 3, 4]] 

Die native json.JSONDecoder Klasse erlaubt mir nicht, das Verhalten der Array-Analyse zu ändern. Ich kann den Parser nur für Objekte (dicts), Ints, Floats, Strings ändern (indem ich Kwargs-Funktionen an JSONDecoder() gebe, siehe the doc).

Also, heißt es, ich muss einen JSON-Parser von Grund auf neu schreiben? Der Python-Code von json ist verfügbar, aber es ist ziemlich durcheinander. Ich würde es vorziehen, seine Interna zu verwenden, anstatt seinen Code zu kopieren!

+0

Überprüfen Sie meine Frage, um zu sehen, wie ich es habe gelöst und folgen Sie es für schnellere Wege: http://stackoverflow.com/questions/17901156/fastest-way-to-convert-javascript-object-array- to-python-dict-list? noredirect = 1 # comment26148131_17901156 –

Antwort

3

Kleine & einfache Abhilfe versuchen:

  1. Convert JSON-ähnliche Daten in Strings.
  2. Ersetzen Sie ",," durch ", null,".
  3. Konvertieren Sie es in was auch immer Ihre Darstellung ist.
  4. Lassen Sie JSONDecoder(), das schwere Heben durchführen.

    1. & 3. kann weggelassen werden, wenn Sie bereits mit Strings arbeiten.

(Und wenn bespannen Umwandlung unpraktisch ist, aktualisieren Sie Ihre Frage mit dieser Info!)

+0

Danke. Ich habe das schon versucht. Ich habe ein einfaches re verwendet, aber manchmal bleiben einige, null 'übrig. Diese Problemumgehung ist ein bisschen zu schmutzig! – zopieux

+0

Leider ist es nicht so einfach wie das, wenn Sie '' ,, '' '', null, '' in ein Komma einfügen, also gehen Sie von '' ,, '' '' '' ' null ,, '' was immer noch fehlschlägt. –

+0

@Lattyware Aber wenn Sie einen Lookbehind verwenden, wie in [meine Antwort] (http://stackoverflow.com/a/10057585/344821), ist alles in Ordnung. Es funktioniert sowieso in Ihrem Beispiel. :) – Dougal

5

Da das, was Sie versuchen, ist zu analysieren, nicht JSON per se, sondern ein eine andere Sprache, die JSON sehr ähnlich ist, benötigen Sie möglicherweise einen eigenen Parser.

Zum Glück ist das nicht so schwer wie es klingt. Sie können einen Python-Parser-Generator wie pyparsing verwenden. JSON kann vollständig mit einer relativ einfachen kontextfreien Grammatik spezifiziert werden (ich fand eine here), also sollten Sie in der Lage sein, sie an Ihre Bedürfnisse anzupassen.

+0

Vielleicht sein Overkill, aber jemand anderes könnte es brauchen! Also +1 von mir! –

+1

+1. Beachten Sie, dass meine Antwort zwar etwas gibt, das funktionieren sollte (soweit ich das beurteilen kann), aber dies ist eine bessere Lösung. Es kann mehr Arbeit sein, aber es wird viel belastbarer sein. Wenn Sie etwas klein machen, dann benutzen Sie meinen Hack, aber wenn Sie etwas Wichtigeres tun, tun Sie es richtig. –

+1

@Lattyware Das ist definitiv richtig. Unser Ansatz wird versagen, wenn beispielsweise in einem String-Objekt aufeinanderfolgende Kommas vorkommen. – Dougal

1

Es ist ein hackischer Weg, es zu tun, aber eine Lösung besteht darin, einfach eine String-Änderung an den JSON-ischen Daten vorzunehmen, um sie in die Reihe zu bringen, bevor sie analysiert wird.

import re 
import json 

not_quite_json = '["foo",,,"bar",[1,,3,4]]' 
not_json = True 
while not_json: 
    not_quite_json, not_json = re.subn(r',\s*,', ', null, ', not_quite_json) 

, die uns verlässt:

'["foo", null, null, "bar",[1, null, 3,4]]' 

Wir können dann tun:

json.loads(not_quite_json) 

Geben Sie uns:

['foo', None, None, 'bar', [1, None, 3, 4]] 

Beachten Sie, dass es nicht so einfach ist als Ersatz , da der Ersatz auch Kommas einfügt, die n können eed ersetzen. Dazu müssen Sie durchschleifen, bis keine Ersetzungen mehr möglich sind. Hier habe ich einen einfachen Regex benutzt, um den Job zu erledigen.

2

Sie können das Komma Ersatz von Lattyware's/przemo_li's Antworten unter Verwendung eines Lookbehind Ausdruck in einem Durchgang tun, das heißt „ersetzen alle Kommas, die nur durch ein Komma vorangestellt“:

>>> s = '["foo",,,"bar",[1,,3,4]]' 

>>> re.sub(r'(?<=,)\s*,', ' null,', s) 
'["foo", null, null,"bar",[1, null,3,4]]' 

Beachten Sie, dass dies funktionieren wird Für kleine Dinge, bei denen Sie beispielsweise annehmen können, dass in String-Literalen keine aufeinanderfolgenden Kommas vorkommen. Im Allgemeinen reichen reguläre Ausdrücke nicht aus, um dieses Problem zu lösen, und Taymon's approach der Verwendung eines echten Parsers ist die einzige vollständig korrekte Lösung.

+0

Ich wusste, dass es einen Weg geben musste, es komplett mit Regexes zu machen, aber ach, es war jenseits von mir. +1, das ist eine bessere Lösung. –

+0

Ihr Code benötigt jedoch eine Korrektur - '' re (r '(? <=,) \ S *,', 'null,') '' sollte '' re (r '(? <= ,), ',' null, ', s) ''. –

+0

@Lattyware Natürlich - Kopieren-Einfügen fehlgeschlagen. – Dougal

1

Ich habe mir die Taymon-Empfehlung angesehen, pyparsing, und ich habe das Beispiel here erfolgreich gehackt, um meine Bedürfnisse zu erfüllen. Es funktioniert gut bei der Simulation von Javascript eval() aber schlägt eine Situation: Nachkommastellen. Es sollte ein optionales abschließendes Komma geben - siehe Tests unten - aber ich kann keine geeignete Methode finden, dies zu implementieren.

from pyparsing import * 

TRUE = Keyword("true").setParseAction(replaceWith(True)) 
FALSE = Keyword("false").setParseAction(replaceWith(False)) 
NULL = Keyword("null").setParseAction(replaceWith(None)) 

jsonString = dblQuotedString.setParseAction(removeQuotes) 
jsonNumber = Combine(Optional('-') + ('0' | Word('123456789', nums)) + 
        Optional('.' + Word(nums)) + 
        Optional(Word('eE', exact=1) + Word(nums + '+-', nums))) 

jsonObject = Forward() 
jsonValue = Forward() 
# black magic begins 
commaToNull = Word(',,', exact=1).setParseAction(replaceWith(None)) 
jsonElements = ZeroOrMore(commaToNull) + Optional(jsonValue) + ZeroOrMore((Suppress(',') + jsonValue) | commaToNull) 
# black magic ends 
jsonArray = Group(Suppress('[') + Optional(jsonElements) + Suppress(']')) 
jsonValue << (jsonString | jsonNumber | Group(jsonObject) | jsonArray | TRUE | FALSE | NULL) 
memberDef = Group(jsonString + Suppress(':') + jsonValue) 
jsonMembers = delimitedList(memberDef) 
jsonObject << Dict(Suppress('{') + Optional(jsonMembers) + Suppress('}')) 

jsonComment = cppStyleComment 
jsonObject.ignore(jsonComment) 

def convertNumbers(s, l, toks): 
    n = toks[0] 
    try: 
     return int(n) 
    except ValueError: 
     return float(n) 

jsonNumber.setParseAction(convertNumbers) 

def test(): 
    tests = (
     '[1,2]',  # ok 
     '[,]',   # ok 
     '[,,]',  # ok 
     '[ , , , ]', # ok 
     '[,1]',  # ok 
     '[,,1]',  # ok 
     '[1,,2]',  # ok 
     '[1,]',  # failure, I got [1, None], I should have [1] 
     '[1,,]',  # failure, I got [1, None, None], I should have [1, None] 
    ) 
    for test in tests: 
     results = jsonArray.parseString(test) 
     print(results.asList()) 
+0

Anstelle von schwarzer Magie mit Doppel-Komma-Strings, können Sie stattdessen einfach die leere Zeichenfolge ein gültiges Element in einer Liste machen? Das scheint mir sauberer zu sein und würde am Ende einer Liste richtig funktionieren. – Taymon

+0

Warten Sie, ich entschuldige mich, ich habe das Problem falsch verstanden. Sie möchten also eine abschließende implizite "Null" vom Ende jeder Liste, die eine hat, abschneiden? – Taymon

+0

Ich denke, dass Ihre Optionen genau das tun sollen (nachdem Sie die Zeichenfolge analysiert haben, entfernen Sie eine abschließende 'Null' aus der Liste, wenn es eine gibt; Sie müssten auch sicher sein, dass sie implizit und nicht literal ist) oder explizit eine Folge erlauben Komma am Ende einer Liste und dann Präzedenz verwenden, um Mehrdeutigkeiten zu vermeiden. – Taymon

Verwandte Themen