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

json_setf does not work on empty json #26

Open
Oelki opened this issue Mar 15, 2018 · 5 comments
Open

json_setf does not work on empty json #26

Oelki opened this issue Mar 15, 2018 · 5 comments

Comments

@Oelki
Copy link

Oelki commented Mar 15, 2018

char json[50] = "{}";
char json_out[50];
struct json_out out = JSON_OUT_BUF(json_out, sizeof(json_out));
json_setf(json, sizeof(json), &out, ".somekey", "true");

only writes the value into json_out and thus creates an invalid json

printf("%s\n", json_out); // outut: "true"

@cpq
Copy link
Member

cpq commented Aug 22, 2018

PR please :)

@marck-247
Copy link

Is this project still active? I am also stuck with the issue as described here...

@cpq
Copy link
Member

cpq commented Aug 29, 2019

Fix and send a PR.

@zdila
Copy link

zdila commented Apr 21, 2020

I were just about to report this issue and found it here :-)

@rtrue983
Copy link

I have a fix for this (at least with how I was using it) but don't have the time ATM to do a PR...

Here are the two functions that need to be modified:

static void json_vsetf_cb(void *userdata, const char *name, size_t name_len,
                          const char *path, const struct json_token *t) {
  struct json_setf_data *data = (struct json_setf_data *) userdata;
  int off, len = get_matched_prefix_len(path, data->json_path);
 *int matched = 0;
  if (t->ptr == NULL) return;
  off = t->ptr - data->base;
  // only check matched later on if deliminator or end are found
  if (len > data->matched &&
      (data->json_path[len] == '.' || data->json_path[len] == '\0')) {
    matched = len;
  }

  /*
   * If there is no exact path match, set the mutation position to be end
   * of the object or array
   */
  if (matched > data->matched && data->pos == 0 &&
      (t->type == JSON_TYPE_OBJECT_END || t->type == JSON_TYPE_ARRAY_END))
  { 
    // only save off matched value if it is a new and larger match
    data->matched = matched;
    // account for a the previous item being a string
    if (data->base[data->prev] == '"') {
      data->pos = data->end = data->prev + 1;
    }
    else {
      data->pos = data->end = data->prev;
    }
  }

  /* Exact path match. Set mutation position to the value of this token */
  if (strcmp(path, data->json_path) == 0 && t->type != JSON_TYPE_OBJECT_START &&
      t->type != JSON_TYPE_ARRAY_START) {
    // need to set pos and end differently to account for '"' around string
    if (t->type == JSON_TYPE_TRUE || t->type == JSON_TYPE_FALSE || t->type == JSON_TYPE_NUMBER) {
      data->pos = off;
      data->end = off + t->len + 1;
    }
    else {
      data->pos = off - 1;
      data->end = off + t->len + 2;
    }
    data->matched = matched;
  }

  /*
   * For deletion, we need to know where the previous value ends, because
   * we don't know where matched value key starts.
   * When the mutation position is not yet set, remember each value end.
   * When the mutation position is already set, but it is at the beginning
   * of the object/array, we catch the end of the object/array and see
   * whether the object/array start is closer then previously stored prev.
   */
  if (data->pos == 0) {
    data->prev = off + t->len; /* pos is not yet set */
  } else if ((t->ptr[0] == '[' || t->ptr[0] == '{') && off + 1 < data->pos &&
             off + 1 > data->prev) {
    data->prev = off + 1;
  }
  (void) name;
  (void) name_len;
}

int json_vsetf(const char *s, int len, struct json_out *out,
               const char *json_path, const char *json_fmt, va_list ap) WEAK;
int json_vsetf(const char *s, int len, struct json_out *out,
               const char *json_path, const char *json_fmt, va_list ap) {
  struct json_setf_data data;
  memset(&data, 0, sizeof(data));
  data.json_path = json_path;
  data.base = s;
  data.end = len;
  json_walk(s, len, json_vsetf_cb, &data);
  if (json_fmt == NULL) {
    /* Deletion codepath */
    json_printf(out, "%.*s", data.prev, s);
    /* Trim comma after the value that begins at object/array start */
    if (s[data.prev - 1] == '{' || s[data.prev - 1] == '[') {
      int i = data.end;
      while (i < len && json_isspace(s[i])) i++;
      if (s[i] == ',') data.end = i + 1; /* Point after comma */
    }
    json_printf(out, "%.*s", len - data.end, s + data.end);
  } else {
    /* Modification codepath */
    int n, off = data.matched+1, depth = 0;
    bool empty_base = false;

    // if a partial or exact match isn't found, adjust a few items
    if (data.matched == 0 && data.pos == 0) {
      data.pos = data.prev - 1;
      data.end--;

      // a completely empty base requres additional correction
      if (data.base[0] == '{' && data.base[1] == '}') {
        empty_base = true;
        data.pos = 1;
        data.prev = 1;
        data.end = 1;
      }
    }

    /* Print the unchanged beginning */
    json_printf(out, "%.*s", data.pos, s);

    /* Add missing keys */
    while ((n = strcspn(&json_path[off], ".[")) > 0) {
      // don't add a comma if it is a completely empty base
      if (s[data.prev - 1] != '{' && s[data.prev - 1] != '[' && depth == 0 && !empty_base) {
        json_printf(out, ",");
      }
      if (off > 0 && json_path[off - 1] != '.') break;
      json_printf(out, "%.*Q:", n, json_path + off);
      off += n;
      if (json_path[off] != '\0') {
        json_printf(out, "%c", json_path[off] == '.' ? '{' : '[');
        depth++;
        off++;
      }
    }
    /* Print the new value */
    json_vprintf(out, json_fmt, ap);

    /* Close brackets/braces of the added missing keys */
    for (; off > data.matched; off--) {
      int ch = json_path[off];
      const char *p = ch == '.' ? "}" : ch == '[' ? "]" : "";
      json_printf(out, "%s", p);
    }

    /* Print the rest of the unchanged string */
    json_printf(out, "%.*s", len - data.end, s + data.end);
  }
  return data.end > data.pos ? 1 : 0;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants