Convert jsonpull to C++ with shared_ptr and std::vector/std::string

Replace the manual malloc/realloc/free memory management in jsonpull
with std::shared_ptr ownership. Each json_object now owns its children
through std::vector<json_object_ptr>; raw back-pointers to parent and
parser remain valid by structural invariant and are cleared on
json_disconnect so detached subtrees can outlive their parser.
Strings become std::string, child arrays become std::vector, and the
old union becomes a struct so non-trivial members can coexist while
preserving the existing o->value.xxx access paths.

The old jsonpull.c is replaced by jsonpull.cpp, json_stringify now
returns std::string, and all callers across tippecanoe, tile-join,
tippecanoe-decode, tippecanoe-json-tool, tippecanoe-overzoom and the
unit tests are updated to use json_object_ptr / json_pull_ptr.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Erica Fischer
2026-05-30 17:48:32 -07:00
co-authored by Cursor
parent 9381158165
commit 3da03c6075
25 changed files with 1295 additions and 531 deletions
+82 -90
View File
@@ -9,7 +9,7 @@
#include "milo/dtoa_milo.h"
#include "text.hpp"
int compare(mvt_value const &one, json_object *two, bool &fail) {
int compare(mvt_value const &one, json_object_ptr two, bool &fail) {
switch (one.type) {
case mvt_string:
if (two->type != JSON_STRING) {
@@ -17,7 +17,7 @@ int compare(mvt_value const &one, json_object *two, bool &fail) {
return false; // string vs non-string
}
return strcmp(one.c_str(), two->value.string.string);
return strcmp(one.c_str(), two->value.string.string.c_str());
case mvt_double:
case mvt_float:
@@ -91,8 +91,8 @@ int compare(mvt_value const &one, json_object *two, bool &fail) {
// 0: false
// 1: true
// -1: incomparable (sql null), treated as false in final output
static int eval(std::function<mvt_value(std::string const &)> feature, json_object *f, std::set<std::string> &exclude_attributes, std::vector<std::string> const &unidecode_data) {
if (f != NULL) {
static int eval(std::function<mvt_value(std::string const &)> feature, json_object_ptr f, std::set<std::string> &exclude_attributes, std::vector<std::string> const &unidecode_data) {
if (f != nullptr) {
if (f->type == JSON_TRUE) {
return 1;
} else if (f->type == JSON_FALSE) {
@@ -110,7 +110,7 @@ static int eval(std::function<mvt_value(std::string const &)> feature, json_obje
}
if (f->type == JSON_STRING) {
if (f->value.string.string[0] == '\0') {
if (f->value.string.string.empty()) {
return 0;
} else {
return 1;
@@ -118,70 +118,70 @@ static int eval(std::function<mvt_value(std::string const &)> feature, json_obje
}
}
if (f == NULL || f->type != JSON_ARRAY) {
fprintf(stderr, "Filter is not an array: %s\n", json_stringify(f));
if (f == nullptr || f->type != JSON_ARRAY) {
fprintf(stderr, "Filter is not an array: %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
if (f->value.array.length < 1) {
fprintf(stderr, "Array too small in filter: %s\n", json_stringify(f));
if (f->value.array.array.size() < 1) {
fprintf(stderr, "Array too small in filter: %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
if (f->value.array.array[0]->type != JSON_STRING) {
fprintf(stderr, "Filter operation is not a string: %s\n", json_stringify(f));
fprintf(stderr, "Filter operation is not a string: %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
if (strcmp(f->value.array.array[0]->value.string.string, "has") == 0 ||
strcmp(f->value.array.array[0]->value.string.string, "!has") == 0) {
if (f->value.array.length != 2) {
fprintf(stderr, "Wrong number of array elements in filter: %s\n", json_stringify(f));
const std::string &op = f->value.array.array[0]->value.string.string;
if (op == "has" ||
op == "!has") {
if (f->value.array.array.size() != 2) {
fprintf(stderr, "Wrong number of array elements in filter: %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
if (strcmp(f->value.array.array[0]->value.string.string, "has") == 0) {
if (op == "has") {
if (f->value.array.array[1]->type != JSON_STRING) {
fprintf(stderr, "\"has\" key is not a string: %s\n", json_stringify(f));
fprintf(stderr, "\"has\" key is not a string: %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
return feature(std::string(f->value.array.array[1]->value.string.string)).type != mvt_no_such_key;
return feature(f->value.array.array[1]->value.string.string).type != mvt_no_such_key;
}
if (strcmp(f->value.array.array[0]->value.string.string, "!has") == 0) {
if (op == "!has") {
if (f->value.array.array[1]->type != JSON_STRING) {
fprintf(stderr, "\"!has\" key is not a string: %s\n", json_stringify(f));
fprintf(stderr, "\"!has\" key is not a string: %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
return feature(std::string(f->value.array.array[1]->value.string.string)).type == mvt_no_such_key;
return feature(f->value.array.array[1]->value.string.string).type == mvt_no_such_key;
}
}
if (strcmp(f->value.array.array[0]->value.string.string, "==") == 0 ||
strcmp(f->value.array.array[0]->value.string.string, "!=") == 0 ||
strcmp(f->value.array.array[0]->value.string.string, ">") == 0 ||
strcmp(f->value.array.array[0]->value.string.string, ">=") == 0 ||
strcmp(f->value.array.array[0]->value.string.string, "<") == 0 ||
strcmp(f->value.array.array[0]->value.string.string, "<=") == 0) {
if (f->value.array.length != 3) {
fprintf(stderr, "Wrong number of array elements in filter: %s\n", json_stringify(f));
if (op == "==" ||
op == "!=" ||
op == ">" ||
op == ">=" ||
op == "<" ||
op == "<=") {
if (f->value.array.array.size() != 3) {
fprintf(stderr, "Wrong number of array elements in filter: %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
if (f->value.array.array[1]->type != JSON_STRING) {
fprintf(stderr, "comparison key is not a string: %s\n", json_stringify(f));
fprintf(stderr, "comparison key is not a string: %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
mvt_value ff = feature(std::string(f->value.array.array[1]->value.string.string));
mvt_value ff = feature(f->value.array.array[1]->value.string.string);
if (ff.type == mvt_no_such_key) {
static bool warned = false;
if (!warned) {
const char *s = json_stringify(f);
fprintf(stderr, "Warning: attribute not found for comparison: %s\n", s);
free((void *) s);
fprintf(stderr, "Warning: attribute not found for comparison: %s\n", json_stringify(f).c_str());
warned = true;
}
if (strcmp(f->value.array.array[0]->value.string.string, "!=") == 0) {
if (op == "!=") {
return true; // attributes that aren't found are not equal
}
return false; // not found: comparison is false
@@ -193,56 +193,54 @@ static int eval(std::function<mvt_value(std::string const &)> feature, json_obje
if (fail) {
static bool warned = false;
if (!warned) {
const char *s = json_stringify(f);
fprintf(stderr, "Warning: mismatched type in comparison: %s\n", s);
free((void *) s);
fprintf(stderr, "Warning: mismatched type in comparison: %s\n", json_stringify(f).c_str());
warned = true;
}
if (strcmp(f->value.array.array[0]->value.string.string, "!=") == 0) {
if (op == "!=") {
return true; // mismatched types are not equal
}
return false;
}
if (strcmp(f->value.array.array[0]->value.string.string, "==") == 0) {
if (op == "==") {
return cmp == 0;
}
if (strcmp(f->value.array.array[0]->value.string.string, "!=") == 0) {
if (op == "!=") {
return cmp != 0;
}
if (strcmp(f->value.array.array[0]->value.string.string, ">") == 0) {
if (op == ">") {
return cmp > 0;
}
if (strcmp(f->value.array.array[0]->value.string.string, ">=") == 0) {
if (op == ">=") {
return cmp >= 0;
}
if (strcmp(f->value.array.array[0]->value.string.string, "<") == 0) {
if (op == "<") {
return cmp < 0;
}
if (strcmp(f->value.array.array[0]->value.string.string, "<=") == 0) {
if (op == "<=") {
return cmp <= 0;
}
fprintf(stderr, "Internal error: can't happen: %s\n", json_stringify(f));
fprintf(stderr, "Internal error: can't happen: %s\n", json_stringify(f).c_str());
exit(EXIT_IMPOSSIBLE);
}
if (strcmp(f->value.array.array[0]->value.string.string, "all") == 0 ||
strcmp(f->value.array.array[0]->value.string.string, "any") == 0 ||
strcmp(f->value.array.array[0]->value.string.string, "none") == 0) {
if (op == "all" ||
op == "any" ||
op == "none") {
bool v;
if (strcmp(f->value.array.array[0]->value.string.string, "all") == 0) {
if (op == "all") {
v = true;
} else {
v = false;
}
for (size_t i = 1; i < f->value.array.length; i++) {
for (size_t i = 1; i < f->value.array.array.size(); i++) {
int out = eval(feature, f->value.array.array[i], exclude_attributes, unidecode_data);
if (out >= 0) { // nulls are ignored in boolean and/or expressions
if (strcmp(f->value.array.array[0]->value.string.string, "all") == 0) {
if (op == "all") {
v = v && out;
if (!v) {
break;
@@ -256,51 +254,47 @@ static int eval(std::function<mvt_value(std::string const &)> feature, json_obje
}
}
if (strcmp(f->value.array.array[0]->value.string.string, "none") == 0) {
if (op == "none") {
return !v;
} else {
return v;
}
}
if (strcmp(f->value.array.array[0]->value.string.string, "in") == 0 ||
strcmp(f->value.array.array[0]->value.string.string, "!in") == 0) {
if (f->value.array.length < 2) {
fprintf(stderr, "Array too small in filter: %s\n", json_stringify(f));
if (op == "in" ||
op == "!in") {
if (f->value.array.array.size() < 2) {
fprintf(stderr, "Array too small in filter: %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
if (f->value.array.array[1]->type != JSON_STRING) {
fprintf(stderr, "\"!in\" key is not a string: %s\n", json_stringify(f));
fprintf(stderr, "\"!in\" key is not a string: %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
mvt_value ff = feature(std::string(f->value.array.array[1]->value.string.string));
mvt_value ff = feature(f->value.array.array[1]->value.string.string);
if (ff.type == mvt_no_such_key) {
static bool warned = false;
if (!warned) {
const char *s = json_stringify(f);
fprintf(stderr, "Warning: attribute not found for comparison: %s\n", s);
free((void *) s);
fprintf(stderr, "Warning: attribute not found for comparison: %s\n", json_stringify(f).c_str());
warned = true;
}
if (strcmp(f->value.array.array[0]->value.string.string, "!in") == 0) {
if (op == "!in") {
return true; // attributes that aren't found are not in
}
return false; // not found: comparison is false
}
bool found = false;
for (size_t i = 2; i < f->value.array.length; i++) {
for (size_t i = 2; i < f->value.array.array.size(); i++) {
bool fail = false;
int cmp = compare(ff, f->value.array.array[i], fail);
if (fail) {
static bool warned = false;
if (!warned) {
const char *s = json_stringify(f);
fprintf(stderr, "Warning: mismatched type in comparison: %s\n", s);
free((void *) s);
fprintf(stderr, "Warning: mismatched type in comparison: %s\n", json_stringify(f).c_str());
warned = true;
}
cmp = 1;
@@ -312,21 +306,21 @@ static int eval(std::function<mvt_value(std::string const &)> feature, json_obje
}
}
if (strcmp(f->value.array.array[0]->value.string.string, "in") == 0) {
if (op == "in") {
return found;
} else {
return !found;
}
}
if (strcmp(f->value.array.array[0]->value.string.string, "attribute-filter") == 0) {
if (f->value.array.length != 3) {
fprintf(stderr, "Wrong number of array elements in filter: %s\n", json_stringify(f));
if (op == "attribute-filter") {
if (f->value.array.array.size() != 3) {
fprintf(stderr, "Wrong number of array elements in filter: %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
if (f->value.array.array[1]->type != JSON_STRING) {
fprintf(stderr, "\"attribute-filter\" key is not a string: %s\n", json_stringify(f));
fprintf(stderr, "\"attribute-filter\" key is not a string: %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
@@ -338,65 +332,63 @@ static int eval(std::function<mvt_value(std::string const &)> feature, json_obje
return true;
}
fprintf(stderr, "Unknown filter %s\n", json_stringify(f));
fprintf(stderr, "Unknown filter %s\n", json_stringify(f).c_str());
exit(EXIT_FILTER);
}
bool evaluate(std::function<mvt_value(std::string const &)> feature, std::string const &layer, json_object *filter, std::set<std::string> &exclude_attributes, std::vector<std::string> const &unidecode_data) {
if (filter == NULL || filter->type != JSON_HASH) {
fprintf(stderr, "Error: filter is not a hash: %s\n", json_stringify(filter));
bool evaluate(std::function<mvt_value(std::string const &)> feature, std::string const &layer, json_object_ptr filter, std::set<std::string> &exclude_attributes, std::vector<std::string> const &unidecode_data) {
if (filter == nullptr || filter->type != JSON_HASH) {
fprintf(stderr, "Error: filter is not a hash: %s\n", json_stringify(filter).c_str());
exit(EXIT_JSON);
}
bool ok = true;
json_object *f;
json_object_ptr f;
f = json_hash_get(filter, layer.c_str());
if (ok && f != NULL) {
if (ok && f != nullptr) {
ok = eval(feature, f, exclude_attributes, unidecode_data) > 0;
}
f = json_hash_get(filter, "*");
if (ok && f != NULL) {
if (ok && f != nullptr) {
ok = eval(feature, f, exclude_attributes, unidecode_data) > 0;
}
return ok;
}
json_object *read_filter(const char *fname) {
json_object_ptr read_filter(const char *fname) {
FILE *fp = fopen(fname, "r");
if (fp == NULL) {
perror(fname);
exit(EXIT_OPEN);
}
json_pull *jp = json_begin_file(fp);
json_object *filter = json_read_tree(jp);
if (filter == NULL) {
json_pull_ptr jp = json_begin_file(fp);
json_object_ptr filter = json_read_tree(jp);
if (filter == nullptr) {
fprintf(stderr, "%s: %s\n", fname, jp->error);
exit(EXIT_JSON);
}
json_disconnect(filter);
json_end(jp);
fclose(fp);
return filter;
}
json_object *parse_filter(const char *s) {
json_pull *jp = json_begin_string(s);
json_object *filter = json_read_tree(jp);
if (filter == NULL) {
json_object_ptr parse_filter(const char *s) {
json_pull_ptr jp = json_begin_string(s);
json_object_ptr filter = json_read_tree(jp);
if (filter == nullptr) {
fprintf(stderr, "Could not parse filter %s\n", s);
fprintf(stderr, "%s\n", jp->error);
exit(EXIT_JSON);
}
json_disconnect(filter);
json_end(jp);
return filter;
}
bool evaluate(std::unordered_map<std::string, mvt_value> const &feature, std::string const &layer, json_object *filter, std::set<std::string> &exclude_attributes, std::vector<std::string> const &unidecode_data) {
bool evaluate(std::unordered_map<std::string, mvt_value> const &feature, std::string const &layer, json_object_ptr filter, std::set<std::string> &exclude_attributes, std::vector<std::string> const &unidecode_data) {
std::function<mvt_value(std::string const &)> getter = [&](std::string const &key) {
auto f = feature.find(key);
if (f != feature.end()) {
@@ -412,7 +404,7 @@ bool evaluate(std::unordered_map<std::string, mvt_value> const &feature, std::st
return evaluate(getter, layer, filter, exclude_attributes, unidecode_data);
}
bool evaluate(mvt_feature const &feat, mvt_layer const &layer, json_object *filter, std::set<std::string> &exclude_attributes, int z, std::vector<std::string> const &unidecode_data) {
bool evaluate(mvt_feature const &feat, mvt_layer const &layer, json_object_ptr filter, std::set<std::string> &exclude_attributes, int z, std::vector<std::string> const &unidecode_data) {
std::function<mvt_value(std::string const &)> getter = [&](std::string const &key) {
const static std::string dollar_id = "$id";
if (key == dollar_id && feat.has_id) {