Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to serialize just the keys or just the values. This would be very useful when both json and csv are needed. #2058

Closed
cfeied opened this issue Feb 20, 2024 · 2 comments
Labels
question v6 ArduinoJson 6

Comments

@cfeied
Copy link

cfeied commented Feb 20, 2024

Hi Benoit,

I ran into an edge situation in which I couldn't quite figure out how to meet my needs with AJ. Perhaps I've just overlooked an existing solution, but I've tried everything I could find in the documentation without success. Here's the story:

In my scenario I send serialized json strings from a medical device to a host console. Everything being sent is a flat structure (zero nesting) where all the keys are const char* and the values could be of any type. AJ has been perfect for this for a long time (with one small exception that we discussed several years ago and for which I have a workaround).

I now need to serialize the exact same data in CSV format -- I'll both transmit it to a host and write it to an SD card.

For obvious reasons I'd prefer not to have to maintain two separate serialization lists -- it would be quite useful to be able to serialize the header row and subsequent data rows directly from the same jsonDocument that I already have.

Iterating over kv pairs of the jsonObject allows me to construct the header because the keys are all of type char*. However, I didn't find any obvious way to invoke serialization on the values, which may be of any jsonvariant type. At first I hoped that kv.value().as<const char*> would do the trick, but that turns out to be a cast and not a serialization. A function like kv.value.serialize() would make the kv pair iteration much more useful.

I would wrap this into calls like serializeCsvKeys(jsonDoc, charArray) and serializeCsvValues(jsonDoc, charArray).

Sadly I'm still on AJ6 because AJ7 pushed me over the limit of my available space on this Teensy 3.6 project.

Thanks for everything!!

@cfeied
Copy link
Author

cfeied commented Feb 21, 2024

I was able to obtain primitive CSV_Keys and CSV_Values from an ArduinoJson document with the following mods to JsonSerializer.hpp:

I added the following variable to the namespace:

int outFmt = 0;

I then renamed visitObject() to visitObjectForJson() and added branching logic in visitObject as follows:

  size_t visitObject(const CollectionData& object) {
      if (outFmt == 1)
          return visitObjectForCsvKeys(object);
      else if (outFmt == 2)
          return visitObjectForCsvValues(object);
      else
          return visitObjectForJson(object);
  }

  size_t visitObjectForJson(const CollectionData& object) {
      {
          write('{');

          VariantSlot* slot = object.head();

          while (slot != 0) {
              _formatter.writeString(slot->key());
              write(':');
              slot->data()->accept(*this);

              slot = slot->next();
              if (slot == 0)
                  break;

              write(',');
          }

          write('}');

          return bytesWritten();
      }
  }


  size_t visitObjectForCsvKeys(const CollectionData& object) {
      VariantSlot* slot = object.head();
      while (slot != 0) {
          _formatter.writeString(slot->key());

          slot = slot->next();
          if (slot == 0)
              break;

          write(',');
      }
      return bytesWritten();
  }

  size_t visitObjectForCsvValues(const CollectionData& object) {
      VariantSlot* slot = object.head();
      while (slot != 0) {
          slot->data()->accept(*this);

          slot = slot->next();
          if (slot == 0)
              break;

          write(',');
      }
      return bytesWritten();
  }

This seems to do exactly what I wanted (for the kind of data and structures where it could make sense), as shown by the following example:

#include <ArduinoJson.h>
#define OUTPUT_PORT Serial

void setup() {
    OUTPUT_PORT.begin(9600);
  while (!OUTPUT_PORT) continue;

  StaticJsonDocument<200> doc;
  char json[] = "{\"SensorType\":\"GPS\",\"Timestamp\":1351824120,\"Latitude\":48.756080,\"Longitude\":2.302038}";
  DeserializationError error = deserializeJson(doc, json);

  if (error) {
       OUTPUT_PORT.print(F("deserializeJson() failed: "));
       OUTPUT_PORT.println(error.f_str());
       return;
  }

  OUTPUT_PORT.print("\n\n--------------- \nSerializeJson with CSV option \n---------------\n");

  OUTPUT_PORT.print("\n\n-----\nARDUINOJSON_NAMESPACE::outFmt == 0 \nJSON string:\n\n");
  ARDUINOJSON_NAMESPACE::outFmt = 0;
  serializeJson(doc, OUTPUT_PORT);

  OUTPUT_PORT.print("\n\n-----\nARDUINOJSON_NAMESPACE::outFmt == 1 \nCSV header row:\n\n");
  ARDUINOJSON_NAMESPACE::outFmt = 1;
  serializeJson(doc, OUTPUT_PORT);

  OUTPUT_PORT.print("\n\n-----\nARDUINOJSON_NAMESPACE::outFmt == 2 \nCSV data row:\n\n");
  ARDUINOJSON_NAMESPACE::outFmt = 2;
  serializeJson(doc, OUTPUT_PORT);
  OUTPUT_PORT.print("\n\n----------\n\n");

}

void loop() 
{
  // nop
}

Producing the following output:

--------------- 
SerializeJson with CSV option 
---------------


-----
ARDUINOJSON_NAMESPACE::outFmt == 0 
JSON string:

{"SensorType":"GPS","Timestamp":1351824120,"Latitude":48.75608,"Longitude":2.302038}

-----
ARDUINOJSON_NAMESPACE::outFmt == 1 
CSV header row:

"SensorType","Timestamp","Latitude","Longitude"

-----
ARDUINOJSON_NAMESPACE::outFmt == 2 
CSV data row:

"GPS",1351824120,48.75608,2.302038

----------

I'm not submitting a pull request because I know you curate your code very meticulously -- there are better ways to achieve this, obviously, and probably plenty of variant cases to think about. I simply put it out here as food for thought.

Thanks!

Craig

@bblanchon
Copy link
Owner

Hi Craig,

A function like kv.value.serialize() would make the kv pair iteration much more useful.

This function exists; it's serializeJson().

size_t n = serializeJson(kv.value(), writePtr);

Then, you can use the return value to advance the writing pointer (ensure you don't overflow).

Best regards,
Benoit

@bblanchon bblanchon added question v6 ArduinoJson 6 and removed enhancement labels Feb 22, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 8, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
question v6 ArduinoJson 6
Projects
None yet
Development

No branches or pull requests

2 participants