cesanta/frozen

json_setf does not work on empty json

Oelki opened this issue · 5 comments

Oelki commented

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 commented

PR please :)

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

cpq commented

Fix and send a PR.

zdila commented

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

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;
}