Pojedyncze a podwójne cudzysłowy w JSON

108

Mój kod:

import simplejson as json

s = "{'username':'dfdsfdsf'}" #1
#s = '{"username":"dfdsfdsf"}' #2
j = json.loads(s)

#1 definicja jest zła

#2 definicja jest właściwa

Słyszałem, że w Pythonie ten pojedynczy i podwójny cudzysłów może być wymienny. Czy ktoś może mi to wyjaśnić?

Bin Chen
źródło

Odpowiedzi:

170

Składnia JSON nie jest składnią Pythona. JSON wymaga podwójnych cudzysłowów dla swoich ciągów.

Ignacio Vazquez-Abrams
źródło
2
ale najpierw to pojedynczy cudzysłów w JSON, jestem zdezorientowany. Ten może przejść kompilację, ale drugi nie.
Bin Chen
6
Dziękuję za to potwierdzenie. Najwyraźniej tylko ja importuję str(dict)i nie chcę evaltego. Prosty .replace("'", '"')powinien załatwić sprawę.
isaaclw
8
I powiedziałem za wcześnie. Najwyraźniej jest to bardziej skomplikowane.
isaaclw
6
Jeśli potrzebujesz używać podwójnych cudzysłowów dookoła, możesz zadzwonić json.dumps(..)dwukrotnie, tak jak w: import json; d = dict(tags=["dog", "cat", "mouse"]); print json.dumps(json.dumps(d))co daje:"{\"tags\": [\"dog\", \"cat\", \"mouse\"]}"
rprasad
124

możesz użyć ast.literal_eval()

>>> import ast
>>> s = "{'username':'dfdsfdsf'}"
>>> ast.literal_eval(s)
{'username': 'dfdsfdsf'}
hahakubile
źródło
9
Najbardziej podoba mi się ta odpowiedź: rzadko masz wybór: jeśli ktoś podaje ci pojedyncze cytaty, masz pojedyncze cudzysłowy. Albo json.loads wymaga dodatkowego argumentu, albo powinieneś go użyć. Globalne zastąpienie „'” to katastrofa, tak jakby przychodzące dane to:{ 'a' : 'this "string" really isn\'t!!!!' }
Mark Gerolimatos
@Mark, czy tę metodę można dostosować do trudniejszej sytuacji z zagnieżdżonymi cudzysłowami, np. "{'link':'<a href="mylink">http://my.com</a>'}"? W tym przypadku ast.literal_evalzgłasza błąd składni
alancalvitti
1
Wydaje mi się to zagrożeniem dla bezpieczeństwa.
JacksonHaenchen
2
Jak to odpowiada na pytanie? Co to ma wspólnego z pojedynczymi i podwójnymi cudzysłowami w JSON? Takie podejście ast może pozwolić na załadowanie dyktu w Pythonie z łańcucha, ale głównym problemem OP jest to, że ciąg nr 1 nie jest prawidłowym kodem JSON, podczas gdy ciąg nr 2 jest.
jschultz410
43

Możesz zrzucić JSON z podwójnym cudzysłowem przez:

import json

# mixing single and double quotes
data = {'jsonKey': 'jsonValue',"title": "hello world"}

# get string with all double quotes
json_string = json.dumps(data) 
cowboybkit
źródło
12
to idzie w złą stronę. serializujesz struktury danych Pythona do formatu JSON; pierwotne pytanie dotyczy deserializacji JSON do struktur danych Pythona.
tedder42
5
Pomysł polegałby na serializacji Pythona do json za pomocą json.dumps, a następnie wywołaniu na nim json.loads, gdy jest w formie str.
jheld
3
Tęsknisz za zrozumieniem tutaj. Jeśli chcesz załadować ciąg json, musi być podwójnym cudzysłowem. To, co robisz, to nadal zrzucanie json, a nie json string.
LegitMe
12

demjson to także dobry pakiet do rozwiązania problemu złej składni json:

pip install demjson

Stosowanie:

from demjson import decode
bad_json = "{'username':'dfdsfdsf'}"
python_dict = decode(bad_json)

Edytować:

demjson.decodejest świetnym narzędziem do uszkodzonego json, ale gdy masz do czynienia z dużą ilością danych json, ast.literal_evaljest to lepsze dopasowanie i znacznie szybsze.

DhiaTN
źródło
4
demjson.decodejest doskonałym narzędziem do uszkodzonego json - ale w przypadku zadań obejmujących dziesiątki lub setki tysięcy pakietów json ast.literal_evaljest znacznie szybsze. Nie znaczy demjsonto , że nie ma swojego miejsca: używam go jako rezerwy na wypadek, gdyby szybsze metody zawiodły.
mjwunderlich
1
Właściwie demjson działa znacznie lepiej, zamiast testować z ast.literal_eval i json.loads
Marware
4

Dwa problemy z udzielonymi dotychczas odpowiedziami, jeśli na przykład jeden z nich przesyła taki niestandardowy JSON. Ponieważ wtedy należałoby zinterpretować przychodzący ciąg znaków (nie słownik Pythona).

Problem 1 - demjson: Z Pythonem 3.7. + I przy użyciu Conda nie mogłem zainstalować demjsona, ponieważ oczywiście obecnie nie obsługuje on Pythona> 3.5. Więc potrzebuję rozwiązania z prostszymi środkami, na przykład asti / lub json.dumps.

Problem 2 - ast& json.dumps: Jeśli JSON jest zarówno w apostrofach, jak i zawiera ciąg w co najmniej jednej wartości, która z kolei zawiera pojedyncze cudzysłowy, jedynym prostym, ale praktycznym rozwiązaniem, które znalazłem, jest zastosowanie obu:

W poniższym przykładzie zakładamy, że linejest to przychodzący obiekt ciągu JSON:

>>> line = str({'abc':'008565','name':'xyz','description':'can control TV\'s and more'})

Krok 1: przekonwertuj przychodzący ciąg znaków na słownik za pomocą ast.literal_eval()
kroku 2: zastosuj się json.dumpsdo niego, aby zapewnić niezawodną konwersję kluczy i wartości, ale bez dotykania zawartości wartości :

>>> import ast
>>> import json
>>> print(json.dumps(ast.literal_eval(line)))
{"abc": "008565", "name": "xyz", "description": "can control TV's and more"}

json.dumpssam nie wykonałby zadania, ponieważ nie interpretuje JSON, a widzi tylko ciąg. Podobnie ast.literal_eval(): chociaż poprawnie interpretuje JSON (słownik), nie konwertuje tego, czego potrzebujemy.

Siegfried Heide
źródło
3

Możesz to naprawić w ten sposób:

s = "{'username':'dfdsfdsf'}"
j = eval(s)
Robin Ali
źródło
użyj ast.literal_eval zamiast eval, aby uniknąć ataków iniekcyjnych
Simon Kingaby
2

Jak powiedziałem, JSON nie jest składnią Pythona. Musisz użyć podwójnych cudzysłowów w JSON. Jego twórca jest (nie) znany z używania ścisłych podzbiorów dopuszczalnej składni, aby złagodzić przeciążenie poznawcze programisty.


Poniżej może się nie powieść, jeśli jeden z ciągów JSON zawiera pojedynczy cudzysłów, jak wskazał @Jiaaro. NIE UŻYWAĆ. Zostawiłem tutaj jako przykład tego, co nie działa.

Jest to bardzo przydatne, aby wiedzieć, że nie istnieją pojedyncze cudzysłowy w ciąg JSON. Powiedzmy, że skopiowałeś i wkleiłeś go z konsoli przeglądarki / cokolwiek. Następnie możesz po prostu wpisać

a = json.loads('very_long_json_string_pasted_here')

W przeciwnym razie może się to zepsuć, jeśli użyje również pojedynczych cudzysłowów.

serv-inc
źródło
2
nie jest prawdą, że w ciągu json nie ma pojedynczych cudzysłowów. Może to być prawdą w konkretnym przypadku, ale nie można na tym polegać. np. to jest poprawne json:{"key": "value 'with' single quotes"}
Jiaaro
2

Naprawdę rozwiązał mój problem za pomocą funkcji eval.

single_quoted_dict_in_string = "{'key':'value', 'key2': 'value2'}"
desired_double_quoted_dict = eval(single_quoted_dict_in_string)
# Go ahead, now you can convert it into json easily
print(desired_double_quoted_dict)
Hafiz Hashim
źródło
To bardzo zły przykład. Co się stanie, jeśli ktoś się dowie, że używasz eval na json i wyśle ​​zniekształcony plik json zawierający kod, który jest następnie oceniany przez eval?
Metonimia
1

Niedawno natknąłem się na bardzo podobny problem i uważam, że moje rozwiązanie zadziała również dla Ciebie. Miałem plik tekstowy, który zawierał listę pozycji w postaci:

["first item", 'the "Second" item', "thi'rd", 'some \\"hellish\\" \'quoted" item']

Chciałem przeanalizować powyższe do listy Pythona, ale nie przepadałem za eval (), ponieważ nie mogłem ufać wejściu. Próbowałem najpierw użyć JSON, ale akceptuje tylko elementy w podwójnych cudzysłowach, więc napisałem własny, bardzo prosty lekser dla tego konkretnego przypadku (po prostu podłącz swój własny „stringtoparse”, a otrzymasz jako listę wyników: „items”)

#This lexer takes a JSON-like 'array' string and converts single-quoted array items into escaped double-quoted items,
#then puts the 'array' into a python list
#Issues such as  ["item 1", '","item 2 including those double quotes":"', "item 3"] are resolved with this lexer
items = []      #List of lexed items
item = ""       #Current item container
dq = True       #Double-quotes active (False->single quotes active)
bs = 0          #backslash counter
in_item = False #True if currently lexing an item within the quotes (False if outside the quotes; ie comma and whitespace)
for c in stringtoparse[1:-1]:   #Assuming encasement by brackets
    if c=="\\": #if there are backslashes, count them! Odd numbers escape the quotes...
        bs = bs + 1
        continue                    
    if (dq and c=='"') or (not dq and c=="'"):  #quote matched at start/end of an item
        if bs & 1==1:   #if escaped quote, ignore as it must be part of the item
            continue
        else:   #not escaped quote - toggle in_item
            in_item = not in_item
            if item!="":            #if item not empty, we must be at the end
                items += [item]     #so add it to the list of items
                item = ""           #and reset for the next item
            continue                
    if not in_item: #toggle of single/double quotes to enclose items
        if dq and c=="'":
            dq = False
            in_item = True
        elif not dq and c=='"':
            dq = True
            in_item = True
        continue
    if in_item: #character is part of an item, append it to the item
        if not dq and c=='"':           #if we are using single quotes
            item += bs * "\\" + "\""    #escape double quotes for JSON
        else:
            item += bs * "\\" + c
        bs = 0
        continue

Mam nadzieję, że komuś się to przyda. Cieszyć się!

Matt
źródło
Czego to zapewnia, czego nie możesz uzyskać z docs.python.org/2/library/ast.html#ast.literal_eval ?
Charles Duffy
-1
import ast 
answer = subprocess.check_output(PYTHON_ + command, shell=True).strip()
    print(ast.literal_eval(answer.decode(UTF_)))

Pracuje dla mnie

vaibhav.patil
źródło
-4
import json
data = json.dumps(list)
print(data)

Powyższy fragment kodu powinien działać.

Dheeraj R
źródło
2
Może zrobić coś pożytecznego, ale nie odpowiada na zadane pytanie. Problem zaczyna się od łańcucha, a nie listy.
Rachel