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
+43 -51
View File
@@ -42,19 +42,19 @@ int mb_geometry[GEOM_TYPES] = {
VT_POLYGON,
};
void json_context(json_object *j) {
char *s = json_stringify(j);
void json_context(json_object_ptr j) {
std::string s = json_stringify(j);
if (strlen(s) >= 500) {
snprintf(s + 497, strlen(s) + 1 - 497, "...");
if (s.size() >= 500) {
s.resize(497);
s.append("...");
}
fprintf(stderr, "in JSON object %s\n", s);
free(s); // stringify
fprintf(stderr, "in JSON object %s\n", s.c_str());
}
void parse_coordinates(int t, json_object *j, drawvec &out, int op, const char *fname, int line, json_object *feature) {
if (j == NULL || j->type != JSON_ARRAY) {
void parse_coordinates(int t, json_object_ptr j, drawvec &out, int op, const char *fname, int line, json_object_ptr feature) {
if (j == nullptr || j->type != JSON_ARRAY) {
fprintf(stderr, "%s:%d: expected array for geometry type %d: ", fname, line, t);
json_context(feature);
return;
@@ -63,7 +63,7 @@ void parse_coordinates(int t, json_object *j, drawvec &out, int op, const char *
int within = geometry_within[t];
if (within >= 0) {
size_t i;
for (i = 0; i < j->value.array.length; i++) {
for (i = 0; i < j->value.array.array.size(); i++) {
if (within == GEOM_POINT) {
if (i == 0 || mb_geometry[t] == VT_POINT) {
op = VT_MOVETO;
@@ -75,13 +75,13 @@ void parse_coordinates(int t, json_object *j, drawvec &out, int op, const char *
parse_coordinates(within, j->value.array.array[i], out, op, fname, line, feature);
}
} else {
if (j->value.array.length >= 2 && j->value.array.array[0]->type == JSON_NUMBER && j->value.array.array[1]->type == JSON_NUMBER) {
if (j->value.array.array.size() >= 2 && j->value.array.array[0]->type == JSON_NUMBER && j->value.array.array[1]->type == JSON_NUMBER) {
long long x, y;
double lon = j->value.array.array[0]->value.number.number;
double lat = j->value.array.array[1]->value.number.number;
projection->project(lon, lat, 32, &x, &y);
if (j->value.array.length > 2) {
if (j->value.array.array.size() > 2) {
static int warned = 0;
if (!warned) {
@@ -121,10 +121,10 @@ void parse_coordinates(int t, json_object *j, drawvec &out, int op, const char *
// type and stringified value. All numeric values, even if they are integers,
// even integers that are too large to fit in a double but will still be
// stringified with their original precision, are recorded here as mvt_double.
serial_val stringify_value(json_object *value, const char *reading, int line, json_object *feature) {
serial_val stringify_value(json_object_ptr value, const char *reading, int line, json_object_ptr feature) {
serial_val sv;
if (value != NULL) {
if (value != nullptr) {
int vt = value->type;
if (vt == JSON_STRING) {
@@ -158,9 +158,7 @@ serial_val stringify_value(json_object *value, const char *reading, int line, js
sv.s = "null";
} else {
sv.type = mvt_string;
const char *v = json_stringify(value);
sv.s = std::string(v);
free((void *) v); // stringify
sv.s = json_stringify(value);
}
}
@@ -178,10 +176,10 @@ static std::vector<mvt_geometry> to_feature(drawvec &geom) {
return out;
}
std::pair<int, drawvec> parse_geometry(json_object *geometry, json_pull *jp, json_object *j,
std::pair<int, drawvec> parse_geometry(json_object_ptr geometry, json_pull_ptr jp, json_object_ptr j,
int z, int x, int y, long long extent, bool fix_longitudes, bool mvt_style) {
json_object *geometry_type = json_hash_get(geometry, "type");
if (geometry_type == NULL) {
json_object_ptr geometry_type = json_hash_get(geometry, "type");
if (geometry_type == nullptr) {
fprintf(stderr, "Filter output:%d: null geometry (additional not reported): ", jp->line);
json_context(j);
exit(EXIT_JSON);
@@ -193,8 +191,8 @@ std::pair<int, drawvec> parse_geometry(json_object *geometry, json_pull *jp, jso
exit(EXIT_JSON);
}
json_object *coordinates = json_hash_get(geometry, "coordinates");
if (coordinates == NULL || coordinates->type != JSON_ARRAY) {
json_object_ptr coordinates = json_hash_get(geometry, "coordinates");
if (coordinates == nullptr || coordinates->type != JSON_ARRAY) {
fprintf(stderr, "Filter output:%d: geometry without coordinates array: ", jp->line);
json_context(j);
exit(EXIT_JSON);
@@ -202,12 +200,12 @@ std::pair<int, drawvec> parse_geometry(json_object *geometry, json_pull *jp, jso
int t;
for (t = 0; t < GEOM_TYPES; t++) {
if (strcmp(geometry_type->value.string.string, geometry_names[t]) == 0) {
if (geometry_type->value.string.string == geometry_names[t]) {
break;
}
}
if (t >= GEOM_TYPES) {
fprintf(stderr, "Filter output:%d: Can't handle geometry type %s: ", jp->line, geometry_type->value.string.string);
fprintf(stderr, "Filter output:%d: Can't handle geometry type %s: ", jp->line, geometry_type->value.string.string.c_str());
json_context(j);
exit(EXIT_JSON);
}
@@ -305,13 +303,13 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
std::map<std::string, mvt_layer> ret;
std::shared_ptr<std::string> tile_stringpool = std::make_shared<std::string>();
json_pull *jp = json_begin_file(fp);
json_pull_ptr jp = json_begin_file(fp);
while (1) {
json_object *j = json_read(jp);
if (j == NULL) {
if (jp->error != NULL) {
json_object_ptr j = json_read(jp);
if (j == nullptr) {
if (jp->error != nullptr) {
fprintf(stderr, "Filter output:%d: %s: ", jp->line, jp->error);
if (jp->root != NULL) {
if (jp->root != nullptr) {
json_context(jp->root);
} else {
fprintf(stderr, "\n");
@@ -319,33 +317,32 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
exit(EXIT_JSON);
}
json_free(jp->root);
jp->root.reset();
break;
}
json_object *type = json_hash_get(j, "type");
if (type == NULL || type->type != JSON_STRING) {
json_object_ptr type = json_hash_get(j, "type");
if (type == nullptr || type->type != JSON_STRING) {
continue;
}
if (strcmp(type->value.string.string, "Feature") != 0) {
if (type->value.string.string != "Feature") {
continue;
}
json_object *properties = json_hash_get(j, "properties");
if (properties == NULL || (properties->type != JSON_HASH && properties->type != JSON_NULL)) {
json_object_ptr properties = json_hash_get(j, "properties");
if (properties == nullptr || (properties->type != JSON_HASH && properties->type != JSON_NULL)) {
fprintf(stderr, "Filter output:%d: feature without properties hash: ", jp->line);
json_context(j);
json_free(j);
exit(EXIT_JSON);
}
std::string layername = "unknown";
json_object *tippecanoe = json_hash_get(j, "tippecanoe");
json_object *layer = NULL;
if (tippecanoe != NULL) {
json_object_ptr tippecanoe = json_hash_get(j, "tippecanoe");
json_object_ptr layer;
if (tippecanoe != nullptr) {
layer = json_hash_get(tippecanoe, "layer");
if (layer != NULL && layer->type == JSON_STRING) {
layername = std::string(layer->value.string.string);
if (layer != nullptr && layer->type == JSON_STRING) {
layername = layer->value.string.string;
}
}
@@ -359,11 +356,10 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
}
auto l = ret.find(layername);
json_object *geometry = json_hash_get(j, "geometry");
if (geometry == NULL) {
json_object_ptr geometry = json_hash_get(j, "geometry");
if (geometry == nullptr) {
fprintf(stderr, "Filter output:%d: filtered feature with no geometry: ", jp->line);
json_context(j);
json_free(j);
exit(EXIT_JSON);
}
@@ -377,8 +373,8 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
feature.type = mb_geometry[t];
feature.geometry = to_feature(dv);
json_object *id = json_hash_get(j, "id");
if (id != NULL && id->type == JSON_NUMBER) {
json_object_ptr id = json_hash_get(j, "id");
if (id != nullptr && id->type == JSON_NUMBER) {
feature.id = id->value.number.number;
if (id->value.number.large_unsigned > 0) {
feature.id = id->value.number.large_unsigned;
@@ -386,7 +382,7 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
feature.has_id = true;
}
for (size_t i = 0; i < properties->value.object.length; i++) {
for (size_t i = 0; i < properties->value.object.keys.size(); i++) {
serial_val sv = stringify_value(properties->value.object.values[i], "Filter output", jp->line, j);
// Nulls can be excluded here because this is the postfilter
@@ -394,18 +390,14 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
if (sv.type != mvt_null) {
mvt_value v = stringified_to_mvt_value(sv.type, sv.s.c_str(), tile_stringpool);
l->second.tag(feature, std::string(properties->value.object.keys[i]->value.string.string), v);
l->second.tag(feature, properties->value.object.keys[i]->value.string.string, v);
}
}
l->second.features.push_back(feature);
}
json_free(j);
}
json_end(jp);
std::vector<mvt_layer> final;
for (auto a : ret) {
final.push_back(a.second);