cesanta/frozen

json_setf doesn's works as expected!

Anricx opened this issue · 3 comments

char *payload = "{\"a\":1}";
struct json_out out = JSON_OUT_BUF(output, output_len);
json_setf(input, input_len, &out, ".sign", "%Q", "5738263BEFF11EC874D99623AC22A3B1");

output:

{"a":1,"sign":"5738263BEFF11EC874D99623AC22A3B1"}

Works grate

char *payload = "{\"service\":\"service.parking.detail\",\"a\":1}";
struct json_out out = JSON_OUT_BUF(output, output_len);
json_setf(input, input_len, &out, ".sign", "%Q", "5738263BEFF11EC874D99623AC22A3B1");

outpu:

{"service":"service.parking.detail","a":1,"59DEE02F6A5CBA08F66FA862BF88EF76"}

Not Working!

cpq commented

PR please :)

zdila commented

In my case input JSON is {"ignore": "me"} (because of #26) and I am calling char *json = "{\"ignore\": \"me\"}"; json_setf(json, strlen(json), &out, ".foo", "{from: %ld, to: %ld, energy: %ld}", rec_start_ts, rec_finish_ts, rec_energy).

The result is: {"ignore": "me,"foo":{"from": 1587467683, "to": 1587558755, "energy": 59}"}

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