Fehler in json Eintrag

Django, Flask, Bottle, WSGI, CGI…
Benutzeravatar
noisefloor
User
Beiträge: 3857
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

such' doch in dem betreffenden Feld nach Einträgen die mit " anfangen. Dann solltest du alle "falschen" Einträge bekommen. Also die, wo in der DB kein JSON Array steht. Damit das in Zukunft nicht mehr passiert solltest du a) deinen Code zum Schreiben in die DB anpassen und b) dir für das Feld ggf. eine Valdidator schreiben, der sicherstellt, dass in das Feld nur ein JSON Array (in Python: eine Liste) geschrieben wird.

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 13121
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Robuster wäre es die Einträge zu suchen die nicht mit [ anfangen. Denn es gibt ja noch andere JSON-Werte die keine Arrays sind.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

__blackjack__ hat geschrieben: Freitag 30. Juni 2023, 17:53 Robuster wäre es die Einträge zu suchen die nicht mit [ anfangen. Denn es gibt ja noch andere JSON-Werte die keine Arrays sind.
Das habe ich aber doch gemacht. Siehe mein Posting weiter oben:
Pitwheazle hat geschrieben: Dienstag 27. Juni 2023, 18:17 ...
Ich habe meinen Code darufhin geändert zu:

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:
            zeile.loesung = json.dumps([zeile.loesung])
            print(zeile.typ, " ", zeile.loesung)
            zeile.save()
    return HttpResponse("fertig!")
...
... diese Einträge sind keine Arrays (Listen) aber trotzdem, wie beschrieben sind es JSON. Diese Änderungen haben zwar dazu geführt, dass mein "migration" ohne Fehler durchgeführt wird. ... aber warum, zum Geier, bekomme ich die Fehlermeldung, es sei kein JSON, wenn es doch nunmal JSON ist - halt nicht in einer Liste sondern eine Zeichenfolge?
Benutzeravatar
__blackjack__
User
Beiträge: 13121
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle: Das hast Du da nicht gemacht. Der Test an der Stelle ist ja schon zu spät, da ist das JSON ja schon dekodiert worden und ein Test auf "[" testet nicht auf ein JSON-Array in der Datenbank sondern ob das in der JSON-Zeichenkette vorkommt. Zumal der Test mit ``not in`` noch weitere Probleme mit sich bring, denn das ist ja nicht robust, selbst wenn man den tatsächlichen Datenbankeintrag an der Stelle hätte. Ein serialisiertes JSON-Array fängt mit der Klammer an. Auch völlig *andere* JSON-Werte können so eine Klammer *irgendwo* in den serialisierten Daten haben.

Und wie schon mal gesagt ist der Code hier falsch: Du serialisierst den alten Wert als JSON und weist diese Zeichenkette `loesung` zu, was dazu führt, dass das serialisierte JSON *noch mal* serialisiert wird. Was in keinem Fall sinnvoll ist.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
noisefloor
User
Beiträge: 3857
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ich halte im gegebenen Fall das automatisierte Vorgehen sowieso für falsch. Wenn es wirklich nur eine paar voll Einträge sind würde die falschen Einträge suchen und dann im Admin-Backend von Django von Hand korrigieren.

Gruß, noisefloor
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Ja, könnte ich machen, da wüsste ich aber nicht, wie ich nach ihnen suche. Ich könnte das aber auch im DB Browser ändern (oder nicht?). Es ist mir aber immer noch nicht klar, worin eigentlich das Problem besteht. Die Fehlermeldung von migrate besagt ja, irgendwo wären Einträge in "loesung" die NULL sind oder kein json - es finden sich aber keine derartigen Einträge (zumindest ist es mir bisher nicht gelungen). Ich habe nur einfach die Einträge, die keine Liste sondern Strings sind ersetzt - JSON waren/sind sie aber.
Benutzeravatar
noisefloor
User
Beiträge: 3857
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

??? - das ist doch schon länger geklärt, dass deine Applikation in dem JSON-Feld immer einen JSON-Array erwartet. Also suchst du, wie __blackjack__ schon gesagt hat, nach allen Einträgen, die nicht mit [ anfangen.

Ungestestetes SQL: SELECT * FROM name_der_tabelle WHERE name_der_json_spalte NOT LIKE "[%"

Zum Arbeiten mit SQLite Datenbankdateien finde ich persönlich SQLite Brwoser sehr praktisch. Gibt's für Linux und Windows.

Gruß, noisefloor
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

noisefloor hat geschrieben: Samstag 1. Juli 2023, 15:04 ??? - das ist doch schon länger geklärt, dass deine Applikation in dem JSON-Feld immer einen JSON-Array erwartet.
Man, mann, geht doch bitte erstmal davon aus, dass ich extrem begriffsstutzig bin. Ich habe jetzt mitbekommen, woran man ein JSON erkennt und was an meinem Code nicht funktioniert - dass meine Applikation immer ein Array erwartet hatte ich noch nicht mitbekommen - ich dachte JSON Zeichenkette sollte auch reichen. Gut, dass du mich da nochmals explizit drauf hinweist (begriffsstutzig und leicht dement!)
noisefloor hat geschrieben: Samstag 1. Juli 2023, 15:04 Ungestestetes SQL: SELECT * FROM name_der_tabelle WHERE name_der_json_spalte NOT LIKE "[%"
... funktioniert nicht (zeigt alle Datensätze an) ich weiß ja sber wo die falschen Einträge erzeugt wurden und

Code: Alles auswählen

SELECT * FROM core_protokoll WHERE kategorie_id = 8 and (typ = 1 or typ = 2 or typ = 4)
zeigt eben diese (zurzeit) 299 Datensätze an.
noisefloor hat geschrieben: Samstag 1. Juli 2023, 15:04 Zum Arbeiten mit SQLite Datenbankdateien finde ich persönlich SQLite Brwoser sehr praktisch.
Den benutze ich.
Muss ich da jetzt 299 Datensätze von Hand ändern oder gibt es dafür eine automatische Option.
Händisch ändern habe ich noch nicht hinbekommen. Es gibt zwar ein Fenster für "Datenbankzelle bearbeiten" - da konnte ich allerdings noch nichts bearbeiten. (Nachtrag: Jetzt geht es unter "SQL ausführen" geht es nicht, unter "Daten durchsuchen" schon)
Benutzeravatar
noisefloor
User
Beiträge: 3857
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
dass meine Applikation immer ein Array erwartet hatte ich noch nicht mitbekommen - ich dachte JSON Zeichenkette sollte auch reichen.
Ähm... die Frage kann nur einer beantworten, nämlich du. Du musst doch wissen, was dein Programm erwartet und weiter verarbeitet. Klar ist beides valides JSON in der DB - aber für dein Programm muss es ja einen Unterschied machen, ob der Rückgabewert aus der DB-Abfrage dann in Python ein String oder eine Liste ist.
Muss ich da jetzt 299 Datensätze von Hand ändern oder gibt es dafür eine automatische Option.
Automatisch im Sinne von automagisch: nein, gibt es nicht. Wenn du _sicher_ bist, das die fehlerhaften Einträge alle gleich falsch sind, dann kannst du das grundsätzlich automatisieren, sprich ein Skript dafür schreiben. Ob das denn schneller und besser ist als 299 Einträge von Hand korrigieren musst du beurteilen.

Und, wie gesagt, wenn in dem JSON-Feld immer ein JSON-Array stehen muss, dann macht IMHO der Aufwand eines Validators Sinn, der sicherstellt, dass das Django ORM nur eine Python Liste als Eingabe für das Feld akzeptiert.

Gruß, noisefloor
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

noisefloor hat geschrieben: Samstag 1. Juli 2023, 15:45 Hallo,
dass meine Applikation immer ein Array erwartet hatte ich noch nicht mitbekommen - ich dachte JSON Zeichenkette sollte auch reichen.
Ähm... die Frage kann nur einer beantworten, nämlich du. Du musst doch wissen, was dein Programm erwartet und weiter verarbeitet. Klar ist beides valides JSON in der DB - aber für dein Programm muss es ja einen Unterschied machen, ob der Rückgabewert aus der DB-Abfrage dann in Python ein String oder eine Liste ist.


Nö, ich wüsste nicht, dass mein Code eine Liste erwarten würde. Ich habe alle meine Codeteile hier eingestellt und definiert ist "loesung" in model so:

Code: Alles auswählen

loesung = models.JSONField() 
und gespeichert wird der Wert im Endeffekt hier:

Code: Alles auswählen

            protokoll = Protokoll.objects.create(
                user = user, titel = titel, sj = user.sj, hj = user.hj, kategorie = kategorie, text = text, pro_text = pro_text, variable = variable, frage = frage, einheit = einheit, 
                anmerkung = anmerkung, wert = ergebnis, [b]loesung = lsg[/b], hilfe_id = hilfe_id, parameter = parameter, individuell = "", wertung = "a", typ = typ, typ2 = typ2, aufgnr = zaehler.aufgnr,        
            ) 
... das war's. Es ist zwar so, dass ich fast überall eine Liste gespeichert habe, nur bei zwei Unterpunkten (typ 1, 2 und 4) in "Zahlenstrahl" habe ich bisher einen String gespeichert.

Angefangen hatte wir in diesem Post etwa so:
sparrow hat geschrieben: Sonntag 25. Juni 2023, 16:39 @Pitwheazle: Das ist ein Integritätsfehler. Du versuchst Migrationen in deine Datenbank einzuspielen, die die Struktur deiner Datenbank so verändern, dass die darin enthaltenen Daten nicht mehr gültig sind. Deshalb wird die Migration mit einem "IntegrityError" abgebrochen.

Im Detail: Irgendwo in dem Feld 'loesung' steht etwas, das kein gültiges JSON ist, oder `null` ist. Und das ist mit der Migration nicht zu vereinbaren, denn nach der Migration ist beides für das Feld ungültig.
und ich habe auch mit eurer Hilfe keinen Eintrag NULL oder "kein gültiges JSON" gefunden.
Aber der Fehler tritt ja auch nicht bei Ausführung meines Codes auf, sondern bei "migrate" - ich habe aber immer noch nicht kapiert warum :(
Benutzeravatar
sparrow
User
Beiträge: 4196
Registriert: Freitag 17. April 2009, 10:28

Warum der Fehler während einer Migration auftritt hatte ich bereits geschrieben.

Wenn du in die Dokumentation von sqlite schaust, wirst du zwei entscheidende Dinge finden.

1.: Die constraints (CHECK) werden ausgewertet, wenn ein Datensatz angelegt oder geupdated wird. Nicht für bestehende Datensätze.

2.: Führen Schemaänderungen unter Umständen dazu, dass Tabellen neu angelegt und die Daten dorthin kopiert werden müssen. Das ist was in diesem Fall passiert. Und da das INSERTs in der neuen Tabelle sind, werden die Constraints ausgewertet.

Warum wie Daten überhaupt falsch in der Datenbank stehen? Möglicherweise war das Feld einmal ohne Constraints, du hast Daten eingefügt. Später wurden die Constraints angelegt.
Benutzeravatar
noisefloor
User
Beiträge: 3857
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
und definiert ist "loesung" in model so:
Die Zeile funktioniert ja auch immer, weil `loesung` einfach an den Rückgabewert von `models.JSONField()` gebunden wird. Spannend wird es erst, wenn danach auf `loesung` zugegriffen wird.

Um das ganze Konsistent zu halten, macht es hier IMHO schon Sinn, dass `loesung` immer ein JSON-Array / eine Python Liste ist. Kann ja auch 1-elementig sein.

Gruß, noisefloor
Benutzeravatar
noisefloor
User
Beiträge: 3857
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

wenn das JSON Feld wirklich immer einen JSON Array (=eine Python Liste) enthalten muss / soll, dann ist der Validator in der models.py ziemlich simpel. Beispiel:

Code: Alles auswählen

from django.db import models
from django.core.exceptions import ValidationError

def check_if_list(value):
    if not isinstance(value, list):
        raise ValidationError("The input has to be a list (=JSON Array)")
    else:
        pass

# Create your models here.
class AppData(models.Model):
    data = models.JSONField(validators=[check_if_list])

    def __str__(self):
        return '{} (type: {})'.format(self.data, type(self.data))
Wenn du von Hand die Einträge korrigierst macht es Sinn, den Validator vorher zu integrieren, dann kann beim Editieren nicht grob schief gehen. Zumindest nicht, was die Datenstruktur betrifft.

Gruß, noisefloor
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Vielen Dank dafür! Ich bin nicht sicher, ob das bei meinem Code so Sinn macht. Mein Eintrag in protokoll.loesung wird ja ganz alleine von meinem Code erzeugt. Ich will es nochmals erklären. Der/die Schüler/in wählt eine Aufgabenkategorie aus und es werden nacheinander 10 Aufgaben erstellt. Der Code speichert dabei alle für diese spezielle Aufgabe benötigten Daten im "Protokoll": Den Aufgabentext, die dazugehörigen Variablen, die Lösung oder auch Lösungen (in besagtem JSON Feld - als Liste), bei einigen Aufgaben zusätzlich auch einen Zahlenwert und einiges mehr (u.U. z.B. einen Link zu einem Hilfetext). Wenn ein Wert angegeben wird, wird auch nur ein Zahlenwert als Eingabe akkzeptiert, ansosnsten überprüft der Code die Lösungen (alles Zeichenketten) in dem Feld "loesung". Der/die Schüler/in oder ich geben keine Werte in "loesung" ein, das macht ganz alleine der Code. Da habe ich jetzt dafür gesorgt, dass nur noch Listen erzeugt werden. Dein Vorschlag könnte ich also einbauen um beim Testen meines Codes einen entsprechenden Hinweis zu bekommen.
Oder habe ich da wieder was falsch verstanden?
Benutzeravatar
noisefloor
User
Beiträge: 3857
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Dein Vorschlag könnte ich also einbauen um beim Testen meines Codes einen entsprechenden Hinweis zu bekommen.
Oder habe ich da wieder was falsch verstanden?
Genau. Der Validator wird _immer_ ausgeführt, wenn über das Model etwas in die DB geschrieben werden soll. Damit hättest du schon mal sichergestellt, dass die (äußere) Datenstruktur im JSON Feld die grundsätzlich richtige ist.

Du könntest den Validator auch noch ausbauen, je nach Notwendigkeit. Wenn die Liste z.B. immer min 3 Elemente enthalten müsste, könnte man auch darauf prüfen bzw. dafür einen 2. Validator bauen.

Gruß, noisefloor
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

So, ich habe das Problem gelöst. Falls es jemanden interessiert:
Ich habe das Ganze umgekehrt angegangen und habe nicht die Datenbank aus dem Internet in mein lokales Projekt eingefügt, ich habe in das Projekt im Internet die geänderten Dateien hochgeladen - das war für's erste mal nur ein view.py und eine CSS Datei. Dann musste ich noch die neue Aufgabenkategorie im Admin Tool hinzufügen. Das hat geklappt.
Im zweiten Durchgang ist es mir dann auch gelungen Änderungen an der Datenbank vorzunehmen. ich habe das einfach zunächst lokal gemacht und dann die entsprechenden Dateien hochgeladen. Ich musste nur aufpassen, dass ich keine Datei vergesse. Auch das hat nach dem einen oder anderen Versuch geklappt (ich hatte halt immer wieder eine Datei vergessen).
Das ganze Problem kam wohl daher, dass ich in meinem lokalen Projekt zu viel rumexperimentiert habe und die Datenbank aus dem Internet sich damit nicht vertragen hat. Die habe ich jetzt gelöscht und zur weiteren Arbeit durch die Projektversion aus dem Internet ersetzt.
Nochmals vielen dank euch allen für eure Hilfe. Ich habe aber wieder viel dazugelernt!
Antworten