Wyświetla lepszy komunikat o błędzie niż „Nie można zdekodować żadnego obiektu JSON”

128

Kod Pythona do ładowania danych z jakiegoś długiego skomplikowanego pliku JSON:

with open(filename, "r") as f:
  data = json.loads(f.read())

(uwaga: najlepsza wersja kodu to:

with open(filename, "r") as f:
  data = json.load(f)

ale oba wykazują podobne zachowanie)

W przypadku wielu typów błędów JSON (brak ograniczników, nieprawidłowe ukośniki odwrotne w ciągach znaków itp.) Powoduje to wyświetlenie miłego, pomocnego komunikatu zawierającego numer wiersza i kolumny, w których znaleziono błąd JSON.

Jednak w przypadku innych typów błędów JSON (w tym klasycznego „używania przecinka na ostatnim elemencie na liście”, ale także innych rzeczy, takich jak pisanie wielkimi literami prawda / fałsz), wynik Pythona to po prostu:

Traceback (most recent call last):
  File "myfile.py", line 8, in myfunction
    config = json.loads(f.read())
  File "c:\python27\lib\json\__init__.py", line 326, in loads
    return _default_decoder.decode(s)
  File "c:\python27\lib\json\decoder.py", line 360, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "c:\python27\lib\json\decoder.py", line 378, in raw_decode
    raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded

W przypadku tego typu wartości ValueError, w jaki sposób Python powie Ci, gdzie jest błąd w pliku JSON?

OJW
źródło
Czy mógłbyś zrzucić fragment swojego pliku?
Ketouem,
Nie próbuję teraz znaleźć błędu w określonym pliku; Próbuję zmodyfikować mój program tak, aby podświetlał błąd w każdym przyszłym czytanym pliku.
OJW
2
Nie jest to bezpośrednio związane, ale możesz po prostu zrobić json.load(f)zamiastjson.loads(f.read())
Martin Samson
@OJW W jakiej wersji Pythona było to zachowanie?
jxramos
Python 3.8.1 podaje teraz pozycję błędu „Wartość oczekiwana: wiersz 1, kolumna 21 (znak 20)”
OJW

Odpowiedzi:

174

Zauważyłem, że simplejsonmoduł daje więcej błędów opisowych w wielu przypadkach, gdy wbudowany jsonmoduł jest niejasny. Na przykład w przypadku przecinka po ostatniej pozycji na liście:

json.loads('[1,2,]')
....
ValueError: No JSON object could be decoded

co nie jest zbyt opisowe. Ta sama operacja z simplejson:

simplejson.loads('[1,2,]')
...
simplejson.decoder.JSONDecodeError: Expecting object: line 1 column 5 (char 5)

Dużo lepiej! Podobnie jest w przypadku innych typowych błędów, takich jak kapitalizowanie True.

Tomek
źródło
19
Przyszłe wersje Pythona będą zawierać te ulepszenia; to ten sam projekt pod spodem.
Martijn Pieters
1
@ user2016290 Bezpośrednia edycja plików rdzenia / pakietów to zły pomysł. Python jest łatwa do małpowania, więc lepiej zrobić to w kodzie.
Rebs
2
@jxramos: OP używał Pythona 2.7, jak wynika z danych śledzenia. Szybki test na ideone.com (Python 3.7.3) pokazuje, że jsonbiblioteka stdlib została zaktualizowana i podaje nowy format komunikatów o błędach. Jednak nie mam teraz czasu na śledzenie dokładnych wydań.
Martijn Pieters
1
@jxramos to znalazł, Python 3.5 zaktualizował wyjątki: bugs.python.org/issue19361 (przez docs.python.org/3/whatsnew/3.5.html#improved-modules ).
Martijn Pieters
15

Nie będziesz w stanie zmusić Pythona, aby powiedział ci, gdzie JSON jest nieprawidłowy. Trzeba będzie użyć LINTER Internecie gdzieś jak ten

Spowoduje to wyświetlenie błędu w pliku JSON, który próbujesz zdekodować.

myusuf3
źródło
2
Czy istnieją narzędzia offline, które mogą to zrobić w przypadku poufnych plików JSON?
OJW
@OJW nie, o których wiem, ale to powinno rozwiązać problem, który masz, lub przynajmniej pozwolić ci naprawić uszkodzony plik JSON.
myusuf3
12
Mój plik JSON jest w porządku - staram się, aby mój program wyświetlał przydatne komunikaty o błędach, które są zrozumiałe dla każdego. Powiedzenie im „pozbądź się tego przecinka w wierszu 13, kolumnie 32” jest dobre. Mówienie im, że „gdzieś w pliku jest błąd, prześlij go do internetu, gdzie ludzie Ci pomogą” jest złe.
OJW
7

Możesz wypróbować bibliotekę rson, którą znajdziesz tutaj: http://code.google.com/p/rson/ . Mam go również na PYPI: https://pypi.python.org/pypi/rson/0.9, więc możesz użyć easy_install lub pip, aby to uzyskać.

na przykład podany przez tom:

>>> rson.loads('[1,2,]')
...
rson.base.tokenizer.RSONDecodeError: Unexpected trailing comma: line 1, column 6, text ']'

RSON został zaprojektowany jako nadzbiór JSON, więc może analizować pliki JSON. Ma również alternatywną składnię, która jest znacznie przyjemniejsza dla ludzi do oglądania i edycji. Używam go dość często do plików wejściowych.

Co do kapitalizacji wartości boolowskich: wydaje się, że rson odczytuje niepoprawnie wielkie wartości logiczne jako ciągi znaków.

>>> rson.loads('[true,False]')
[True, u'False']
Brad Campbell
źródło
4

Miałem podobny problem i było to spowodowane pojedynczymi cytatami. Standard JSON ( http://json.org ) mówi tylko o używaniu podwójnych cudzysłowów, więc musi być tak, że jsonbiblioteka Pythona obsługuje tylko podwójne cudzysłowy.

Rycerz Samar
źródło
3

W przypadku mojej konkretnej wersji tego problemu przeszukałem deklarację funkcji load_json_file(path)w packaging.pypliku, a następnie przemyciłem printdo niej wiersz:

def load_json_file(path):
    data = open(path, 'r').read()
    print data
    try:
        return Bunch(json.loads(data))
    except ValueError, e:
        raise MalformedJsonFileError('%s when reading "%s"' % (str(e),
                                                               path))

W ten sposób wydrukowałby zawartość pliku json przed wejściem do try-catch, iw ten sposób - nawet przy mojej ledwo istniejącej wiedzy o Pythonie - mogłem szybko zorientować się, dlaczego moja konfiguracja nie mogła odczytać pliku json.
(To dlatego, że skonfigurowałem mój edytor tekstu do pisania BOM UTF-8… głupi)

Wspomnę tylko o tym, ponieważ choć może nie była to dobra odpowiedź na konkretny problem PO, była to raczej szybka metoda określenia źródła bardzo uciążliwego błędu. I założę się, że wiele osób natknie się na ten artykuł, którzy szukają bardziej szczegółowego rozwiązania dla MalformedJsonFileError: No JSON object could be decoded when reading …. To może im pomóc.

WoodrowShigeru
źródło
Powinieneś użyć menedżera kontekstu dla pliku I / O ( with open(fn) as f), obsługuje on zamknięcie pliku w wyjątkowych sytuacjach. en.wikibooks.org/wiki/Python_Programming/…
Rebs
1
+1. Gdybyś mógł pokazać przykład, jak wtedy obserwowałeś to małpą w standardowym zachowaniu, byłoby to całkiem fajne
Craig Brett
Przepraszam, nigdy nie dotknąłem żadnego kodu Pythona po rozwiązaniu tego problemu. Może ktoś inny może pomóc?
WoodrowShigeru
3

Jeśli chodzi o mnie, mój plik json jest bardzo duży, gdy używa się wspólnego jsonw Pythonie, otrzymuje powyższy błąd.

Po zainstalowaniu simplejsonprzez sudo pip install simplejson.

A potem to rozwiązałem.

import json
import simplejson


def test_parse_json():
    f_path = '/home/hello/_data.json'
    with open(f_path) as f:
        # j_data = json.load(f)      # ValueError: No JSON object could be decoded
        j_data = simplejson.load(f)  # right
    lst_img = j_data['images']['image']
    print lst_img[0]


if __name__ == '__main__':
    test_parse_json()
Jayhello
źródło
1

Miałem podobny problem, to był mój kod:

    json_file=json.dumps(pyJson)
    file = open("list.json",'w')
    file.write(json_file)  

    json_file = open("list.json","r")
    json_decoded = json.load(json_file)
    print json_decoded

problem polegał na tym, że zapomniałem o file.close() tym, że to zrobiłem i naprawiłem problem.

Habib Kazemi
źródło
U mnie też zadziałało, nie wiem, dlaczego wcześniej nie miałem tego problemu.
pceccon
Powinieneś użyć menedżera kontekstu dla pliku I / O ( with open(fn) as f), obsługuje on zamknięcie pliku w wyjątkowych sytuacjach. en.wikibooks.org/wiki/Python_Programming/…
Rebs
0

Zaakceptowana odpowiedź jest najłatwiejsza do rozwiązania problemu. Ale w przypadku, gdy nie możesz zainstalować simplejson ze względu na politykę Twojej firmy, proponuję poniższe rozwiązanie, aby rozwiązać konkretny problem „używania przecinka na ostatniej pozycji na liście” :

  1. Utwórz klasę podrzędną „JSONLintCheck”, aby dziedziczyć z klasy „JSONDecoder” i nadpisać metodę init klasy „JSONDecoder”, jak poniżej:

    def __init__(self, encoding=None, object_hook=None, parse_float=None,parse_int=None, parse_constant=None, strict=True,object_pairs_hook=None)        
            super(JSONLintCheck,self).__init__(encoding=None, object_hook=None,      parse_float=None,parse_int=None, parse_constant=None, strict=True,object_pairs_hook=None)
            self.scan_once = make_scanner(self)
    
  1. make_scanner to nowa funkcja, która zastępowała metodę „scan_once” powyższej klasy. A oto kod na to :
  1 #!/usr/bin/env python
  2 from json import JSONDecoder
  3 from json import decoder
  4 import re
  5
  6 NUMBER_RE = re.compile(
  7     r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
  8     (re.VERBOSE | re.MULTILINE | re.DOTALL))
  9
 10 def py_make_scanner(context):
 11     parse_object = context.parse_object
 12     parse_array = context.parse_array
 13     parse_string = context.parse_string
 14     match_number = NUMBER_RE.match
 15     encoding = context.encoding
 16     strict = context.strict
 17     parse_float = context.parse_float
 18     parse_int = context.parse_int
 19     parse_constant = context.parse_constant
 20     object_hook = context.object_hook
 21     object_pairs_hook = context.object_pairs_hook
 22
 23     def _scan_once(string, idx):
 24         try:
 25             nextchar = string[idx]
 26         except IndexError:
 27             raise ValueError(decoder.errmsg("Could not get the next character",string,idx))
 28             #raise StopIteration
 29
 30         if nextchar == '"':
 31             return parse_string(string, idx + 1, encoding, strict)
 32         elif nextchar == '{':
 33             return parse_object((string, idx + 1), encoding, strict,
 34                 _scan_once, object_hook, object_pairs_hook)
 35         elif nextchar == '[':
 36             return parse_array((string, idx + 1), _scan_once)
 37         elif nextchar == 'n' and string[idx:idx + 4] == 'null':
 38             return None, idx + 4
 39         elif nextchar == 't' and string[idx:idx + 4] == 'true':
 40             return True, idx + 4
 41         elif nextchar == 'f' and string[idx:idx + 5] == 'false':
 42             return False, idx + 5
 43
 44         m = match_number(string, idx)
 45         if m is not None:
 46             integer, frac, exp = m.groups()
 47             if frac or exp:
 48                 res = parse_float(integer + (frac or '') + (exp or ''))
 49             else:
 50                 res = parse_int(integer)
 51             return res, m.end()
 52         elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
 53             return parse_constant('NaN'), idx + 3
 54         elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
 55             return parse_constant('Infinity'), idx + 8
 56         elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
 57             return parse_constant('-Infinity'), idx + 9
 58         else:
 59             #raise StopIteration   # Here is where needs modification
 60             raise ValueError(decoder.errmsg("Expecting propert name enclosed in double quotes",string,idx))
 61     return _scan_once
 62
 63 make_scanner = py_make_scanner
  1. Lepiej umieść funkcję „make_scanner” razem z nową klasą potomną w tym samym pliku.
Jeremy Li
źródło
0

Po prostu trafiłem na ten sam problem iw moim przypadku problem był związany z BOM(znacznikiem kolejności bajtów) na początku pliku.

json.tool odmówiłby przetwarzania nawet pustego pliku (tylko nawiasów klamrowych), dopóki nie usunę znaku BOM UTF.

To, co zrobiłem, to:

  • otworzyłem mój plik json z vimem,
  • usunięto znak kolejności bajtów ( set nobomb)
  • Zapisz plik

To rozwiązało problem z json.tool. Mam nadzieję że to pomoże!

Tomasz W
źródło
-1

Kiedy tworzony jest plik. Zamiast tworzyć plik z zawartością jest pusty. Zamienić:

json.dump({}, file)
Hoang Duong
źródło
-3

Możesz użyć cjson , który twierdzi, że jest do 250 razy szybszy niż implementacje w czystym Pythonie, biorąc pod uwagę, że masz "jakiś długi, skomplikowany plik JSON" i prawdopodobnie będziesz musiał go uruchomić kilka razy (dekodery zawodzą i zgłaszają pierwszy błąd tylko spotkanie).

yahe
źródło