Welcher eingebettete Json Schlüssel wurde übertragen?

Hallo Zusammen,

ich finde zu folgender Thematik leider keine Beispiele und hoffe daher auf hilfreiches Feedback von Euch:

Der ESP32 arbeitet in meinem Projekt als Server und Websocketserver. Die darauf gehostete html Seite enthält etwa 30 Eingabefelder. Der Benutzer, der die Seite aufruft, kann die in diesen Feldern voreingetragenen Werte ändern. Tut er das, erfolgt eine Übertragung des entsprechenden Json Schlüssel:Wert Paares an den Server, der die Wertänderung speichern soll.
Jetzt die Schwierigkeit: Alle Beispiele, die ich gefunden habe, gehen m.E. davon aus, dass bekannt ist, welche Paare übertragen werden, nämlich alle erwarteten.
In meinem Fall wird jedoch jeweils nur genau ein Schlüssel:Wert Paar übertragen. Wie kann ich denn beim Empfang des Paares überprüfen, welcher Schlüssel enthalten ist, so dass ich im nächsten Schritt den enthaltenen Wert auslesen und der richtigen Variable im RAM zuweisen kann?

Die Paare, die übertragen werden können sehen so aus, wie unten aufgelistet. Neben einfachen Schlüssel:Wert Paaren gibt es auch ein Array und zwei eingebettete Objekte.

{
  "timeGateOpen": "07:00",
  "timeGateClose": "23:00",
  "gateModeFixedTime": true,
  "tempShouseDay": 12,
  "tempShouseNight": 10,
  "timeTempTransSunrise": 60,
  "timeTempTransSunset": 30,
  "tempGhouse": 23,
  "tempHystGhouse": 2,
  "timeOffsetLampSunrise": 90,
  "timeOffsetLampSunset": 90,
  "timeOffsetGateSunrise": 0,
  "timeOffsetGateSunset": 60,
  "geopos": [
    "N",
    49.79762631,
    "O",
    9.92513869
  ],
  "tempSensors": {
    "sensorCountShouse": 2,
    "sensorCountGhouse": 2,
    "tempSensorTolSHouse": 0.7,
    "tempModeSHouse": "AVG",
    "tempModeGHouse": "MAX"
  },
  "tempSensorAddr": {
    "sh1": "",
    "sh2": "",
    "gh1": "",
    "gh2": "",
    "acdc5v": "",
    "acdc12v": ""
  },
  "dateLastChanges": "04.02.2025"
}

Es wird aber immer nur eins übertragen, also z.B. eines aus dem tempSensors Objekt:

tempSensors": "sensorCountShouse": 2

weil der Benutzer ja immer nur einen Wert auf der Webseite ändern kann und mit Return abschickt.
Verstehe ich das überhaupt richtig? Wird wirklich nur das eine Paar (oder eigentlich ja Triple) übertragen oder wird immer das komplette Objekt ab root übertragen, in diesem Fall also

 "tempSensors": {
    "sensorCountShouse": 2,
    "sensorCountGhouse": 2,
    "tempSensorTolSHouse": 0.7,
    "tempModeSHouse": "AVG",
    "tempModeGHouse": "MAX"
  }

?

Welcher root Schlüssel übertragen wird, sollte ich laut https://arduinojson.org/v7/how-to/list-keys-in-a-jsondocument/ so herausfinden:

for (JsonPair kv : doc.as<JsonObject>()) {
  Serial.println(kv.key().c_str());  
}

Nun habe ich im Falle des tempSensors Objektes aber noch eine weitere eingebettete Ebene. Wie finde ich denn heraus, welcher Schlüssel auf der zweiten Ebene es ist, der übertragen wurde? Nur nach Kenntnis beider Schlüssel kann ich den folgenden Wert serverseitig der richtigen Variable zuweisen.

Im Voraus vielen Dank für Eure Tipps!

Werner

Das Vorgehen sehe ich als sehr kompliziert an. Einfacher wäre es den ganzen JSON zu übertragen. Warum willst du ihn nicht vollständig übertragen?

Hier ein Link zur AJ Doku. Es kann getestet werden, ob ein Key enthalten ist.
containsKey

Zunächst musst du rausfinden, wie das JSON exakt aufgebaut wenn die Daten übertragen werden. Das was du als Antwort gezeigt hast, ist kein valides JSON.

Wenn dann feststeht, auf welcher Ebene das Key Value Pair steht sollte eine Prüfung mit z.B.

 if (doc.containsKey("sensorCountShouse"))

funktionieren (wenn doc den richtigen Kontext hat).

Danke an alle für Eure Anregungen!

@Plumps, ja, nachdem ich eine Nacht drüber geschlafen habe, scheint es mir auch zu kompliziert. Ich wollte nicht das gesamte serialisierte Json vom Client zum Server übertragen, nachdem der User einen Wert geändert hat, einfach, weil 97% des Inhaltes identisch ist mit dem, was ich zuvor vom Server zum Client übertragen habe, nachdem der Client die Verbindungsanfrage gestellt hat.
In Anbetracht der relativ geringen Datenmenge und der Tatsache, dass ja nur bei Werteänderungen auf der html Seite vom Client zum Server übertragen wird, wäre das vermutlich kein gewichtiges Argument.
Ich habe mir außerdem überlegt, dass ich einfach ohne eingebettete Objekte arbeiten könnte. Das einbetten hat in diesem Fall m.E. nur einen kosmetischen Zweck, d.h. die Paare sind thematisch etwas zusammengefasst. Da das parsen dann aber sehr kompliziert zu werden droht, werde ich wohl alle Paare auf eine Ebene legen, also ohne Einbettung.

@amshh, danke für den Link. Hier müsste ich w.c. alle möglichen Schlüssel durchtesten um den enthaltenen zu finden. Zugegeben, die Anzahl der Schlüssel ist überschaubar. Ich hatte gehofft, es könnte umgekehrt laufen, dass ich den oder die enthaltenen Schlüssel herausziehen kann und dann die Schlüsselpaare gezielt ansprechen und die Werte speichern kann. Falls das aber nicht geht wäre das eine Möglichkeit.

@noiasca, was ich oben gezeigt habe und mit "{timeGateOpen" beginnt ist das, was ich mit

String SerializeConfig() { // This creates the string containing all the Json key:value pairs, Json arrays and nested Json structures containing all the config.parameters 
  String json;
  JsonDocument doc; // This creates the JsonDocument "doc"
  
  JsonObject root1 = doc.to<JsonObject>();
  JsonObject root2 = doc.to<JsonObject>();

  doc["timeGateOpen"] = timeGateOpen; // Time of day (hh:mm) when greenhouse door gets opened
  doc["timeGateClose"] = timeGateClose; // Uhrzeit zu der das Gewächshaustor geschlossen wird.
  doc["gateModeFixedTime"] = gateModeFixedTime; // Feste Uhrzeiten (true) oder sonnenstand gesteuert
  doc["tempShouseDay"] = tempShouseDay; // Soll-Temperatur im Schlafhaus nach Sonnenauf- und vor Sonnenuntergang für die Steuerung der Deckelheizung
  doc["tempShouseNight"] = tempShouseNight; // Soll-Temperatur im Schlafhaus nach Sonnenunter- und vor Sonnenaufgang für die Steuerung der Deckelheizung
  doc["timeTempTransSunrise"] = timeTempTransSunrise; // Übergangszeit in Minuten nach Sonnenaufgang, während der die Solltemperatur im Schlafhaus von  Nacht auf Tag angepasst wird.
  doc["timeTempTransSunset"] = timeTempTransSunset; // Übergangszeit vor und nach Sonnenaufgang in Minuten, während der die Solltemperatur im Schlafhaus von Nacht auf Tag angepasst wird.
  doc["tempGhouse"] = tempGhouse; // Solltemperatur im Gewächshaus unterhalb der die Wärmelampe aktiviert wird
  doc["tempHystGhouse"] = tempHystGhouse; // Temp.hysterese zwischen Einschalt- und Ausschalttemp. der Wärmelampe. 
  doc["timeOffsetLampSunrise"] = timeOffsetLampSunrise; // Wärmelampe wird um timeOffsetLampSunrise Minuten nach Sonnenaufgang aktiviert, sofern die Gewächshaustemp. niedriger ist als tempGhouse
  doc["timeOffsetLampSunset"] = timeOffsetLampSunset; // Wärmelampe wird um timeOffsetLampSunset Minuten vor Sonnenuntergang deaktiviert.
  doc["timeOffsetGateSunrise"] = timeOffsetGateSunrise; // Öffnen des Tors um timeOffsetGateSunrise Minuten nach Sonnenaufgang. Falls Wert negativ, wird Tor vor Sonnenaufgang geöffnet. 
  doc["timeOffsetGateSunset"] = timeOffsetGateSunset; // Schließen des Tors um timeOffsetGateSunset Minuten nach Sonnenuntergang. Falls Wert negativ, wird Tor vor Sonnenuntergang geschlossen. 
  doc["timeZone"] = timeZone; // Zeitzone relativ zu GMT
  doc["daylightSavings"] = daylightSavings; // 0 = keine Sommerzeit, 1 = Sommerzeit, 2 = Autokmatische Umstellung
  // Json Array für die geographische Position
  doc["geoPos"][0] = geoPos.ns;
  doc["geoPos"][1] = geoPos.latitude;
  doc["geoPos"][2] = geoPos.ow;
  doc["geoPos"][3] = geoPos.longitude;

  JsonObject sensors = root1["tempSensors"].to<JsonObject>();

  sensors["sensorCountShouse"] = tempSensors.sensorCountShouse;
  sensors["sensorCountGhouse"] = tempSensors.sensorCountGhouse;
  sensors["tempSensorTolSHouse"] = tempSensors.tempSensorTolSHouse;
  sensors["tempModeSHouse"] = tempSensors.tempModeSHouse;
  sensors["tempModeGHouse"] = tempSensors.tempModeGHouse;

   // JsonObject root = doc.to<JsonObject>();
  JsonObject addr = root2["tempSensorAddr"].to<JsonObject>();
  addr["sh1"] = tempSensorAddr.sh1;
  addr["sh2"] = tempSensorAddr.sh2;
  addr["gh1"] = tempSensorAddr.gh1; 
  addr["gh2"] = tempSensorAddr.gh2; 
  addr["acdc5v"] = tempSensorAddr.acdc5v;
  addr["acdc12v"] = tempSensorAddr.acdc12v;
    
  doc["dateLastChanges"] = dateLastChanges; // Datum der letzten Änderung des Inhaltes der JSON Konfig.Datei
  // Die o.g. Strukturen haben eine Größe von 512byte, 768byte sollten für die Größe des JsonDocuments verwendet werden
  serializeJson(doc, json);
  Serial.println(json); 
  return json;
}

erzeugt und dann zwecks besserer Lesbarkeit über einen der online Json Fomatierer mit Zeilenumbrüchen habe formatieren lassen.
Deshalb dachte ich eigentlich, dass dieser Json String ok ist. Was passt denn nicht?

Werner

das erste ist ein JSON.

Das zweite - wo du beschreibst was vieleicht retour käme - das ist kein valides JSON.

Wir sehen nicht was du an Daten erhältst. Das musst du rausfinden und posten. Danach können wir dir einen Code Vorschlag machen.

@noiasca, ok, danke. Ich hoffe, am Wochenende kriege ich die Webseite so weit, dass sie bei Werteänderungen ein Json zurücksendet. Da ich die Einbettung voraussichtlich weglasse, weil ich's sonst nicht hinkriege, wird aller Voraussicht nach jeweils ein einzelnes Schlüssel:Wert Paar kommen. Wir werden sehen...

Jein .....
Wenn es denn so sein soll:
Du könntest immer zwei KV-Paare übertragen. Nämlich das, von dem du immer berichtest und ein zweites, in dem steht WAS DAS ERSTE PAAR enthält. Im einfachsten Fall einfach als ein int-Wert.
(timeGateOpen = 0, timeGateClose=1 ... usw).
Und mit diesem empfangenen Wert adressierst du ein Array of struct, das du vorher in deinem Programm statisch aufgebaut hast.
Und in dem struct steht der Key (z.B. "timeGateOpen") sowie ein Pointer auf den Ort, wo der Value-Wert abzulegen ist. Das ist dann ein ArduinoJson-Zugriff.

Natürlich müssen Array und int-Liste im Javascript genau übereinstimmen, sonst geht es in die Hose.

In der Art: Security broken by design

@amshh, klingt toll aber habe ich nicht verstanden.
Die Idee, die mir allerdings beim lesen gekommen ist, war folgende: im ersten Paar, welches mit bspw. dem Schlüssel "wasaendertsich" jedes Mal übertragen wird, da gebe ich als Wert den Schlüssel des sich aufgrund einer Benutzereingabe geänderten Paares ein. Beispiel: 1. Paar: "wasaendert sich":"timeGateOpen". 2. Paar: "timeGateOpen":"10:00"
Da muss ich dann jedes Mal nur zwei Paare lesen, nämlich wasaendertsich und dann weiß ich, welches Paar zusätzlich übertragen wurde und kann den dann gezielt ansprechen. Coole Idee, falls es das ist, was Du meintest.

Moin @wmauss ,

wenn ich es recht verstanden habe, suchst Du nach einer Möglichkeit, JSON Keys aus einem Datensatz auszulesen und den Wert (value) dann passend zuzuordnen ...

Die Keys und ihre zugehörigen Werte können durch Iteration ausgelesen werden:
(Quelle: https://arduinojson.org/v6/api/jsonobject/begin_end/ )

DynamicJsonDocument doc(1024);
deserializeJson(doc, "{\"first\":\"hello\",\"second\":\"world\"}");
JsonObject root = doc.as<JsonObject>();

for (JsonPair kv : root) {
    Serial.println(kv.key().c_str());
    Serial.println(kv.value().as<const char*>());
}

Danach solltest Du den/die Werte korrekt zuordnen können, ggf. braucht es eine Typwandlung.

Gruß
ec2021

Nein, kannst du nicht wirklich. Du bekommst dann im ersten KV (Key/Value) eben einen dummen String, in dem der Name des folgenden Wertes (in deinem Beispiel "timeGateOpen") steht.
Und dann?
Dann fängst du wieder an, diesen String mit allen möglichen Strings zu vergleichen, denn das Programm weiß ja nicht von sich aus, was der String "timeGateOpen" bedeutet, bzw. in welche Variable der Wert abzulegen ist.
Daher der Gedanke, das per Index in ein vorbereitetes Array an das Programm zu melden. Dann ein Zugriff in das Array mit diesem Index und du kannst
a) mittels ArduinoJson den KV-Wert holen
b) den Wert direkt über den Pointer ablegen.

Wenn es sein muss, kannst du in dem struct auch noch kodiert hinterlegen, ob der zu lesende Wert z.b. ein Integer oder float oder String ist und davon abhängig deinen ArduinoJson Zugriff steuern sowie deine Speichermethode. (Int und float als Zuweisung, String mittels strcpy() )
usw. usw.