Fehler in json Eintrag

Django, Flask, Bottle, WSGI, CGI…
Benutzeravatar
__blackjack__
User
Beiträge: 13199
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle: Wieso weisst Du nicht wie man in der Datenbank ungültiges JSON aufspürt? Du hast Dir doch explizit gültiges JSON selektieren lassen. Was denkst Du denn was die `json_valid()`-Funktion macht? Was gibt die denn bei ungültigem JSON zurück? Das kannst Du doch als Bedingung bei einem SELECT verwenden.

Die rot markierten Werte sind alle gültiges JSON.

@Kebap Der Datenbank ist das nicht egal. Da ist ein ``CHECK(JSON_VALID("lösung") OR lösung IS NULL)`` als Constraint auf der Tabelle. *Das* hat doch zu der Ausnahme geführt.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Pitwheazle
User
Beiträge: 896
Registriert: Sonntag 19. September 2021, 09:40

Kebap hat geschrieben: Dienstag 27. Juni 2023, 14:32 Der Datenbank ist ja erstmal egal, ob der Text darin gültiges JSON ist. Im Zweifel kennt die gar kein JSON. Die gibt nur den Text aus.
Dein Python Code empfängt den Text aus der Datenbank und versucht, den als JSON zu interpretieren und in Python Objekte umzuwandeln.
Ich bin nicht sicher, ob dein Code dabei irgendwie besonders gnädig mit dem JSON umgeht, so dass es zu keinen Problemen bisher kam.

Unklar ist auch, wer dort überhaupt ursprünglich den Text eingefügt hatte, der dann später nicht als JSON zu interpretieren ist.
Vielleicht brauchst du also auch noch Anpassungen an (Teilen von) dem Python Code, der die Texte in die Datenbank schreibt.
Danke für die Anteilnahme:
Zum ersten Teil: Ich vermute, dass mein Code den Text nur als Text bzw. Liste übernimmt und JSON ihn nicht direkt tangiert.
Zum zweiten teil: Den Text hat ganz sicher mein Code eingefügt und ich habe ja schon geschrieben, dass ich diesen Fehler ändern werde.
Mich wundert nur, dass es nur mit den Daten aus dem Internet zu diesem Fehler kommt und die Migration hier lokal den Fehler nicht erzeugt - da sind diese Datensätze ja auch drin.
Pitwheazle
User
Beiträge: 896
Registriert: Sonntag 19. September 2021, 09:40

Schade, das hat (noch) nicht geklappt.
Ich habe zunächst folgenden Code ausgeführt:

Code: Alles auswählen

def update(req):
    protokoll = Protokoll.objects.all()
    for zeile in protokoll.iterator():
        if  (isinstance(zeile.loesung, str)):
            print(zeile.loesung)
    return HttpResponse("fertig!")
und bekam alle entsprechenden Einträge angezeigt.
Dann:

Code: Alles auswählen

def update(req):
    protokoll = Protokoll.objects.all()
    for zeile in protokoll.iterator():
        if (isinstance(zeile.loesung, str)):
            zeile.loesung = [zeile.loesung]
            print(zeile.loesung)
            zeile.save()
    return HttpResponse("fertig!")
... das hat geklappt und:

Code: Alles auswählen

def update(req):
    protokoll = Protokoll.objects.all()
    for zeile in protokoll.iterator():
        if not (isinstance(zeile.loesung, list)):
            print(zeile.loesung)
    return HttpResponse("fertig!")
... zeigt nichts - also sind alle Strings in Listen verwandelt und das kann ich im DB Browser auch sehen ... JSON scheinen sie aber nicht zu sein - es erfolgt die bekannte Fehlermeldung:

Code: Alles auswählen

Operations to perform:
  Apply all migrations: accounts, admin, auth, contenttypes, core, sessions
Running migrations:
  Applying core.0092_remove_sachaufgabe_ergebnis_sachaufgabe_anmerkung_and_more...Traceback (most recent call last):
  File "C:\Users\Pit\AppData\Local\Programs\Python\Python310\lib\site-packages\django\db\backends\utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
  File "C:\Users\Pit\AppData\Local\Programs\Python\Python310\lib\site-packages\django\db\backends\sqlite3\base.py", line 416, in execute
    return Database.Cursor.execute(self, query, params)
sqlite3.IntegrityError: CHECK constraint failed: (JSON_VALID("loesung") OR "loesung" IS NULL)
Was mache ich nun?
Pitwheazle
User
Beiträge: 896
Registriert: Sonntag 19. September 2021, 09:40

Juhu - ich bin ganz stolz - ich glaube ich habe es geschafft:

Code: Alles auswählen

def update(req):
    protokoll = Protokoll.objects.all()
    for zeile in protokoll.iterator():
        if (isinstance(zeile.loesung, str)):
            zeile.loesung = json.dumps([zeile.loesung])
            print(zeile.loesung)
            zeile.save()
    return HttpResponse("fertig!")
Benutzeravatar
__blackjack__
User
Beiträge: 13199
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle: Ich glaube eher nicht, denn jetzt hast Du in einem JSON-Feld, das ja *selbst* die Konvertierung vornimmt, eine Zeichenkette gespeichert die JSON enthält, also am Ende *doppelt* kodiert ist. Also beispielsweise ursprünglich so etwas wie "42" ist "[\"42\"]" in der Datenbank, wo eigentlich ["42"] stehen sollte. Alle drei Werte sind gültiges JSON, aber nur einen davon willst Du anscheinend haben, und einen ganz sicher nicht, nämlich "[\"42\"]", weil der so gar keinen Sinn macht.

Und alles was Du da gemacht hast funktioniert nur wenn von Anfang an gültiges JSON da drin stand, denn sonst hätte ja schon das laden des Datensatzes zu einem Dekodierungsfehler geführt. Oder spätestens der lesende Zugriff auf `zeile.loesung`, denn das JSON-Feld dekodiert den Inhalt dann ja und das geht nur wenn's gültiges JSON ist.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Pitwheazle
User
Beiträge: 896
Registriert: Sonntag 19. September 2021, 09:40

Ich hatte schon in einem Probeversuch gemerkt, dass da was nicht stimmt. Mein Code lokal hat dieses ausgegeben:
Bild
... allerdings wirklich nur in meiner lokalen Version in meiner Testinstallation mit den Daten aus dem Internet kamen diese ["[\"\"]"] nicht vor.
Ich habe meinen Code darufhin geändert zu:

Code: Alles auswählen

def update(req):
    if not req.user.is_superuser:
        return HttpResponse("Zugriff verweigert")
    protokoll = Protokoll.objects.all()
    for zeile in protokoll.iterator():
        if (isinstance(zeile.loesung, str)) and "[" not in zeile.loesung:
            zeile.loesung = json.dumps([zeile.loesung])
            print(zeile.typ, " ", zeile.loesung)
            zeile.save()
    return HttpResponse("fertig!")
das war zwar sicher wieder stümperhaft, hatte aber als Ausgabe:
Bild
... ist das besser?
Auf jeden Fall läuft "migrate" ohne Fehlermeldung und das war ja bezweckt. Im Endeffekt muss ich auf diese 20000 Daten ja auch nicht wirklich zugreifen. Wichtig ist für mich, dass die Aufgabe aufgerufen wird (in diesem Protokoll-Objekt wird ja hauptsächlich die aktuelle Aufgabe gespeichert) und ohne Fehler ausgewertet wird und dass die Statistik stimmt.
Muss ich mir noch Sorgen machen?
Benutzeravatar
__blackjack__
User
Beiträge: 13199
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle: Warum postest Du denn jetzt Textausgaben als Bild‽

Ich denke mittlerweile kann hier niemand mehr wirklich nachvollziehen was Du auf welcher Datenbank in welchem Zustand machst.

Die zweite Ausgabe ist falsch weil das jetzt alles JSON-Strings sind die JSON-kodierte Daten enthalten, was wie schon mal gesagt, so überhaupt keinen Sinn macht.

Das sieht man übrigens auch nur weil `repr()` auf Zeichenketten in der Regel ' als Begrenzer nimmt, weshalb man das von einer Liste unterscheiden kann. Wenn man Ausgaben zur Fehlersuche macht und da wirklich sinnvoll Zahlen, Zeichenketten, und Listen unterscheiden möchte, müsste man das mit `repr()` umwandeln bevor man es mit `print()` ausgibt.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Pitwheazle
User
Beiträge: 896
Registriert: Sonntag 19. September 2021, 09:40

__blackjack__ hat geschrieben: Dienstag 27. Juni 2023, 18:34 @Pitwheazle: Warum postest Du denn jetzt Textausgaben als Bild‽
Entschuldige
__blackjack__ hat geschrieben: Dienstag 27. Juni 2023, 18:34 Ich denke mittlerweile kann hier niemand mehr wirklich nachvollziehen was Du auf welcher Datenbank in welchem Zustand machst.
Das ist aber doch nicht so schwer. Meine lokale Installation funktioniert und auch der "migrate" Befehl erzeugt keine Fehler. Und hier habe ich auch dafür gesorgt dass alle "loesungen" in eine Liste eingebettet sind.

Parallel habe ich eine Kopie in der ich die sqlLite Datenbank aus der laufenden Internet Installation eingefügt habe und die will mit den Änderungen aus der lokalen Installation nicht so wie sie soll. Ich konnte die Änderungen nicht vornehmen, da "migrate" einen Fehler auswirft und damit alle Änderungen blockiert. Mit meinem Code habe ich das geändert und es funktioniert soweit ich das jetzt beurteilen kann. Hier waren ja doch anscheinend Einträge, die nicht JSON konform waren und nach meinem Update sind sie es ja anscheinend.
__blackjack__ hat geschrieben: Dienstag 27. Juni 2023, 18:34 Das sieht man übrigens auch nur weil `repr()` auf Zeichenketten in der Regel ' als Begrenzer nimmt, weshalb man das von einer Liste unterscheiden kann. Wenn man Ausgaben zur Fehlersuche macht und da wirklich sinnvoll Zahlen, Zeichenketten, und Listen unterscheiden möchte, müsste man das mit `repr()` umwandeln bevor man es mit `print()` ausgibt.
'repr()' kannte ich noch nicht, das muss ich jetzt mal ausprobieren.

Hast du denn einen Vorschlag was ich ändern soll?
Benutzeravatar
sparrow
User
Beiträge: 4231
Registriert: Freitag 17. April 2009, 10:28

Naja, __blackjack__ hat schon Recht. Ich bin abgehängt.

Eine grunsätzliche Bemerkung: Migrationen in Django erstellen und verändern das Schema der Datenbank. Im günstigsten Fall sind sie so geschrieben, dass sie auch wieder zurückgerollt werden können. Werden alle MIgrationen auf eine leere Datenbank angewendet, muss anschließend die Struktur der Datenbank dem der Modelle in den Apps entsprechen.
Das muss sauber gemacht und gehandhabt werden.
Die Benennung der Verzeichnisse lässt vermuten, dass du kein Versionsmanagementsystem verwendest. Das würde dabei helfen Migrationen und Models in atomaren Commits "synchron" zu halten.
Dass das Problem bei dir überhaupt auftritt, kann eigentlich nur daran liegen, dass du das nicht gemacht hast. Und dass du wahrscheinlich verschiedene Codebasen verwendest.
Jetzt hast du Migrationena aus dem Projekt per Hand gelöscht. Das heißt aber auch, dass du hoffentlich keinen Datenbestand hast, in dem die bereits angewendet waren. Das geht natürlich wieder schief.

Wenn man das Django ORM verwendet und die Modelle führend für die Datenbankstruktur sind, ist das korrekte Arbeiten mit den Migrationen absolut essentiell.
Benutzeravatar
__blackjack__
User
Beiträge: 13199
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle: Du hast nichts an der JSON-Konformität geändert, denn Du hast kein ungültiges JSON in gültiges JSON geändert. Das geht mit dem gezeigten Quelltext ja gar nicht, denn der benutzt das Django-ORM um auf das JSON-Feld zuzugreifen und das würde einem um die Ohren fliegen wenn dort versucht wird ungültiges JSON zu dekodieren.

Es wäre halt immer noch mal interessant was genau SQLite3 da nicht mag. Dazu würde ich die Datensätze abfragen deren `lösung`-Spalte nach Meinung von SQLite3 kein valides JSON enthalten.

Alternativ, wenn der Inhalt dieser Spalte nicht weiter interessiert, würde ich da keinen offensichtlichen Müll reinschreiben, sondern beispielsweise ``null`` (JSON) direkt in der Datenbank beziehungsweise ``None`` (Python) wenn das über Python-Code und das Django-ORM passiert.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Pitwheazle
User
Beiträge: 896
Registriert: Sonntag 19. September 2021, 09:40

__blackjack__ hat geschrieben: Dienstag 27. Juni 2023, 20:45 Es wäre halt immer noch mal interessant was genau SQLite3 da nicht mag. Dazu würde ich die Datensätze abfragen deren `lösung`-Spalte nach Meinung von SQLite3 kein valides JSON enthalten.
Wenn du mir sagst, wie man das macht, würde ich das gerne tun. Google hat mir nicht geholfen und mein Informationstechnik-Sohn auch nicht.
__blackjack__ hat geschrieben: Dienstag 27. Juni 2023, 20:45 Alternativ, wenn der Inhalt dieser Spalte nicht weiter interessiert, würde ich da keinen offensichtlichen Müll reinschreiben, sondern beispielsweise ``null`` (JSON) direkt in der Datenbank beziehungsweise ``None`` (Python) wenn das über Python-Code und das Django-ORM passiert.
Wenn ich feststelle, dass mein Konstrukt nicht geht, hatte ich das auch schon überlegt.
Benutzeravatar
Kebap
User
Beiträge: 695
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Danke für die Korrektur. Das war mir nicht bewusst.
Ich kann übrigens auch die Bilder hier nicht sehen, nur den Text. Das erklärt aber manche "Lücken" in den Beschreibungen.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Pitwheazle
User
Beiträge: 896
Registriert: Sonntag 19. September 2021, 09:40

sparrow hat geschrieben: Dienstag 27. Juni 2023, 20:42 Die Benennung der Verzeichnisse lässt vermuten, dass du kein Versionsmanagementsystem verwendest. Das würde dabei helfen Migrationen und Models in atomaren Commits "synchron" zu halten.
Doch, ich benutze Git, kann aber damit nicht so gut umgehen (wer hätte es gedacht!). Meinen Bennung (rechentrainer-024) rührt daher, dass ich versucht habe, der Version, die ich hochgeladen habe eine neue Versionsnummer zu geben. Alledings habe ich die im Internet einmal, bei den Sachaufgaben dort eine kleine Änderung vorgenommen und auch dort ein migarte durchgeführt. Da das bei den Sachaufgaben passiert ist, führt möglicherweise mein Problem genau daher - da stimmt wahrscheinlich eure Vermutung.
sparrow hat geschrieben: Dienstag 27. Juni 2023, 20:42 Jetzt hast du Migrationena aus dem Projekt per Hand gelöscht. Das heißt aber auch, dass du hoffentlich keinen Datenbestand hast, in dem die bereits angewendet waren. Das geht natürlich wieder schief.
Das habe ich in meiner Verzweiflung nur in einer Kopie ausprobiert und in keiner der beiden laufenden Installationen.
__blackjack__ hat geschrieben: Dienstag 27. Juni 2023, 20:45 Es wäre halt immer noch mal interessant was genau SQLite3 da nicht mag. Dazu würde ich die Datensätze abfragen deren `lösung`-Spalte nach Meinung von SQLite3 kein valides JSON enthalten.
Das habe ich leider immer noch nicht hinbekommen.
__blackjack__ hat geschrieben: Dienstag 27. Juni 2023, 20:45 Alternativ, wenn der Inhalt dieser Spalte nicht weiter interessiert, würde ich da keinen offensichtlichen Müll reinschreiben, sondern beispielsweise ``null`` (JSON) direkt in der Datenbank beziehungsweise ``None`` (Python) wenn das über Python-Code und das Django-ORM passiert.
Nun ja, es ist nicht direkt Müll. Ich habe ja schon geschrieben, dass nach Anwendung des Codes die Migration funktioniert hat. Vielleicht ist das ja nur Zufall, dass ich von den über 20 000 Einträge etwa 30 von "1234" in ["1234"] geändert habe. Der Eintrag ist ja dadurch kein Müll. ich kann die entsprechenden Einträge im Template "Protokoll" ja einsehen. Mein Code hat nur in zwei Unterthemen der Kategorie "Zahlenstrahl" als "loesung" einen String eingetragen und nicht diesen String in eine Liste eingetragen. Zwischenzeitlich habe ich das in meinem Code geändert und jetzt werden grundsätzlich alle Lösungen als Liste im JSON Feld "loesung" eingetragen.
Die "alten" Werte, die mein Code geändert haben und die "neuen" Werte, die gespeichert werden nachdem ich meinen Code geändert habe werden in meinem Template identisch angezeigt. Also zB. wird die Antwort auf "Schreibe folgende Zahl in Ziffern: Dreihundertfünfundfünfzigtausendacht" als ["355 008"] angezeigt.
Ich sehe mit meinen Mitteln keinen Unterschied und das Ganze funktioniert bisher ja auch ohne Fehlermeldung - meint ihr nicht, ich könnte das jetzt so lassen?
Benutzeravatar
__blackjack__
User
Beiträge: 13199
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wenn man "1234" in ["1234"] ändert ist das natürlich kein Müll. Wenn man das allerdings als JSON kodiert und Django das dann *noch mal* als JSON kodiert, dann steht Müll in der Datenbank. Dass das nicht direkt zu einer Fehlermeldung führt, heisst ja nicht dass das korrekt ist, denn sinnvoll als Liste verarbeiten kann man das dann nicht mehr, weil es dann keine Liste mit einer Zeichenkette mehr ist, sondern eine Zeichenkette die eine Darstellung eines JSON-Arrays enthält.

Falls dieses Feld grundsätzlich eine Liste mit Werten speichern soll, dann musst Du wenn ich die gezeigten Beispieldaten richtig interpretiere, schauen ob aus den Feldern auf Python-Seite eine Liste oder eine Zeichenkette geliefert wird. Falls es eine Liste ist, dann ist es hoffentlich okay. Falls es eine Zeichenkette ist, wird es schwieriger, denn Du hast da anscheinend sowohl einzelne Zeichenketten mit Eingaben, wie "355 008", die man in eine Liste stecken müsste, als auch Zeichenketten mit serialisierten JSON-Arrays, die man deserialisieren müsste um an die tatsächliche Liste zu kommen.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Pitwheazle
User
Beiträge: 896
Registriert: Sonntag 19. September 2021, 09:40

Nun, ich habe ja schon geschrieben, dass ich grundsätzlich Listen in dem Feld eintrage ... eigentlich - in drei Unterfunktionen einer Aufgabenkategorie wurden Zahlen als Zeichenkette gespeichert. Das war eine überschaubare Anzahl, diese habe ich zumindest versucht mit obigem Code zu überschreiebn und seit daher bekomme ich keine Fehlermeldung.

Ich habe jetzt nochmals meine Datenbank untersucht. Du/ihr hast/habt Recht. Hier unten im Sreenshot ist zu erkennen, dass mein besagter Umwandlungscode die Einträge wie z.B. "7900" in "[\"7900\"]" umgewandelt haben (rot markiert]. Dies ist bei den Untertypen 1,2 und 4 passiert. Nachdem ich meinen Programmcode für diese Typen geändert habe, werden die Lösungen jetzt ordentlich gespeichert (grüne Markiereungen):
Bild
Die Frage ist jetzt aber weiterhin: Was mache ich damit? Nach der Umwandlung funktioniert mein "migrate" mit der Datenbank aus dem Internet und ohne nicht.

Mir fällt als Lösung jetzt ein, dass ich meinen "Umwandlungscode so anpassen könnte, dass er bei diesen Einträgen NULL einträgt - jetzt bin ich aber unsicher, ob, und wenn ja, wie ich das formuliere.

Gibt es denn hier jemanden, der mir sagen kann, wie ich z.B. mittels DB Browser diese Einträge daraufhin überprüfen kann, ob sie JSON Konform sind (Also nicht die von mir geänderten sondern die Einträge vor der Änderung - die ich sicherheitshalber in einer Kopie gespeichert habe)? Und diese könnte ich ja dann eventuell im DB Browser dementsprechend ändern?
Benutzeravatar
grubenfox
User
Beiträge: 435
Registriert: Freitag 2. Dezember 2022, 15:49

Pitwheazle hat geschrieben: Freitag 30. Juni 2023, 15:05 Gibt es denn hier jemanden, der mir sagen kann, wie ich z.B. mittels DB Browser diese Einträge daraufhin überprüfen kann, ob sie JSON Konform sind (Also nicht die von mir geänderten sondern die Einträge vor der Änderung - die ich sicherheitshalber in einer Kopie gespeichert habe)? Und diese könnte ich ja dann eventuell im DB Browser dementsprechend ändern?
Vielleicht die Datenbank...
https://database.guide/sqlite-json_valid/
Pitwheazle
User
Beiträge: 896
Registriert: Sonntag 19. September 2021, 09:40

@grubenfox:
Jetzt müsste ich das noch anwenden können. Also ein Schnellschuss mit:

Code: Alles auswählen

def update(req):
    protokoll = Protokoll.objects.all()
    for zeile in protokoll.iterator():
        if (isinstance(zeile.loesung, str)) and "[" not in zeile.loesung:
            if not json_valid(zeile.loesung):
            	print(zeile.typ, " ", zeile.loesung)
    return HttpResponse("fertig!")
... geht natürlich nicht :(
Benutzeravatar
grubenfox
User
Beiträge: 435
Registriert: Freitag 2. Dezember 2022, 15:49

Python im DB Browser? Das wundert mich nicht, dass da nichts geht...
Pitwheazle
User
Beiträge: 896
Registriert: Sonntag 19. September 2021, 09:40

Ich habe das nicht im DB Browser angewandt sondern in meinem Django Code - OK besser kann ich es alleine nicht.
Wie wende ich das im DB Browser auf alle Einträge in "loesung" innerhalb von "core.protokoll" an?
Also irgendwie so ähnlich: "select * from core_protokoll where loesung is json_valid()" oder "select * from core_protokoll where is json_valid(loesung)"? ... geht natürlich beides nicht :( .
Antworten