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

YAML to JsonObject #1808

Closed
tobozo opened this issue Oct 4, 2022 · 4 comments
Closed

YAML to JsonObject #1808

tobozo opened this issue Oct 4, 2022 · 4 comments
Labels

Comments

@tobozo
Copy link

tobozo commented Oct 4, 2022

hey @bblanchon and thanks for your awesome library !

Ref.

given the differences/similarities between JSON and YAML, how far is serializeJsonPretty from producing yml output, and how hard would it be to produce an ArduinoYAML library based on your awesome work ?

your answer:

serializeYaml() would be easy, but deserializeYaml() would require a lot of work!

deserializeYaml() is now implemented: https://github.com/tobozo/esp32-yaml

I wish I could produce the serializeYaml() and serializeYmlPretty() for ArduinoJson too but the level of C++ in this library is way beyond my skills.

@bblanchon
Copy link
Owner

Hi @tobozo,

For serializeYaml(), you need to walk the JsonDocument recursively, as shown in the "Recursive Analyzer" case study in Mastering ArduinoJson.
In summary, you must first test the type of the variant; if it's an object, enumerate the key-value pairs and do a recursive call; if it's an array, enumerate the elements, etc.

Best regards,
Benoit

@tobozo
Copy link
Author

tobozo commented Oct 7, 2022

thanks for your helpful feedback and marvelous documentation 👍

I came up with this recursive function and got a satisfying result, did I miss anything?

[edit] pasted the non-leaky version + yaml multiline helper

enum JNestingType_t { NONE, SEQ_KEY, MAP_KEY };

#define indent(x) std::string(x*2, ' ').c_str()

// multiline helper
void yaml_multiline_escape_string( Stream* stream, const char* str, size_t length, size_t *bytes_out, size_t depth )
{
  size_t i;
  char c;
  char l = '\0';

  String _str = String(str);
  bool has_multiline = _str.indexOf('\n') > -1;
  if( has_multiline ) *bytes_out += stream->printf("|\n%s",  indent(depth) );

  for (i = 0; i < length; i++) {
    c = *(str + i);
    if( c== '\r' || c=='\n' ) {
      if( l != '\\' && has_multiline ) { // unescaped \r or \n
        if(c == '\r') *bytes_out += 1;  // ignore \r
        else          *bytes_out += stream->printf("\n%s",  indent(depth) ); // print CRLF
      } else {
        if(c == '\n') *bytes_out += stream->printf("\\n");
        else          *bytes_out += stream->printf("\\r");
      }
    } else {
      if( has_multiline ) {
        *bytes_out += stream->printf("%c", c);
      } else {
        if      (c == '\\') *bytes_out += stream->printf("\\\\");
        else if (c == '\0') *bytes_out += stream->printf("\\0");
        else if (c == '\b') *bytes_out += stream->printf("\\b");
        else if (c == '\t') *bytes_out += stream->printf("\\t");
        else                *bytes_out += stream->printf("%c", c);
      }
    }
    l = c; // memoize last char to spot escaped entities
  }
}

// JsonVariant deconstructor => YAML stream
size_t serializeYml_JsonVariant( JsonVariant root, Stream &out, int depth=0, JNestingType_t nt=NONE )
{
  int parent_level = depth>0?depth-1:0;
  size_t out_size = 0;
  if (root.is<JsonArray>()) {
    JsonArray array = root;
    for( size_t i=0; i<array.size(); i++ ) {
      size_t child_depth = array[i].is<JsonObject>() ? depth+1 : depth-1;
      out_size += serializeYml_JsonVariant(array[i], out, child_depth, SEQ_KEY);
    }
  } else if (root.is<JsonObject>()) {
    JsonObject object = root;
    int i = 0;
    for (JsonPair pair : object) {
      bool is_seqfirst = (i++==0 && nt==SEQ_KEY);
      out_size += out.printf("\n%s%s%s: ", is_seqfirst ? indent(parent_level) : indent(depth), is_seqfirst ? "- " : "", pair.key().c_str() );
      out_size += serializeYml_JsonVariant( pair.value(), out, depth+1, MAP_KEY );
    }
  } else if( !root.isNull() ) {
    if( nt == SEQ_KEY ) out_size += out.printf("\n%s%s",  indent(depth), "- " );
    yaml_multiline_escape_string(&out, root.as<String>().c_str(), root.as<String>().length(), &out_size, depth);
  } else {
    // Error, root is null
  }
  return out_size;
}

@bblanchon
Copy link
Owner

Yep, that's the idea.

@tobozo
Copy link
Author

tobozo commented Oct 7, 2022

Since most data processing softwares bundle their own codecs when exporting to other formats I mostly wrote this function for ArduinoJson (e.g. as an extra example), but just noticed the label change so I guess it's too much out of scope?

Please let me know if you want a PR for this, I'll add the function to my esp32-yaml library anyway.

thanks again for your support!

@tobozo tobozo closed this as completed Oct 7, 2022
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 19, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants