diff --git a/attribute.cpp b/attribute.cpp index 2794e5b7..e4deb932 100644 --- a/attribute.cpp +++ b/attribute.cpp @@ -55,20 +55,19 @@ void set_attribute_accum(std::unordered_map &attribut exit(EXIT_JSON); } - for (size_t i = 0; i < o->keys().size(); i++) { - json_object_ptr k = o->keys()[i]; - json_object_ptr v = o->values()[i]; - - if (k->type != JSON_STRING) { + size_t i = 0; + for (const auto &e : o->entries()) { + if (e.key->type != JSON_STRING) { fprintf(stderr, "%s: -E%s: key %zu not a string\n", *argv, arg, i); exit(EXIT_JSON); } - if (v->type != JSON_STRING) { + if (e.value->type != JSON_STRING) { fprintf(stderr, "%s: -E%s: value %zu not a string\n", *argv, arg, i); exit(EXIT_JSON); } - set_attribute_accum(attribute_accum, k->string().c_str(), v->string().c_str()); + set_attribute_accum(attribute_accum, e.key->string().c_str(), e.value->string().c_str()); + i++; } return; diff --git a/dirtiles.cpp b/dirtiles.cpp index de3f86d4..452ecf9e 100644 --- a/dirtiles.cpp +++ b/dirtiles.cpp @@ -260,14 +260,14 @@ sqlite3 *dirmeta2tmp(const char *fname) { exit(EXIT_JSON); } - for (size_t i = 0; i < o->keys().size(); i++) { - if (o->keys()[i]->type != JSON_STRING || o->values()[i]->type != JSON_STRING) { + for (const auto &e : o->entries()) { + if (e.key->type != JSON_STRING || e.value->type != JSON_STRING) { fprintf(stderr, "%s: non-string in metadata\n", name.c_str()); } - char *sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES (%Q, %Q);", o->keys()[i]->string().c_str(), o->values()[i]->string().c_str()); + char *sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES (%Q, %Q);", e.key->string().c_str(), e.value->string().c_str()); if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) { - fprintf(stderr, "set %s in metadata: %s\n", o->keys()[i]->string().c_str(), err); + fprintf(stderr, "set %s in metadata: %s\n", e.key->string().c_str(), err); } sqlite3_free(sql); } diff --git a/geojson.cpp b/geojson.cpp index 4d16088f..66cad85b 100644 --- a/geojson.cpp +++ b/geojson.cpp @@ -175,24 +175,22 @@ int serialize_geojson_feature(struct serialization_state *sst, json_object_ptr g } } - size_t nprop = 0; - if (properties != nullptr && properties->type == JSON_HASH) { - nprop = properties->keys().size(); - } - std::vector> full_keys; std::vector values; - - full_keys.reserve(nprop); - values.reserve(nprop); key_pool key_pool; - for (size_t i = 0; i < nprop; i++) { - if (properties->keys()[i]->type == JSON_STRING) { - serial_val sv = stringify_value(properties->values()[i], sst->fname, sst->line, feature); + if (properties != nullptr && properties->type == JSON_HASH) { + const auto &entries = properties->entries(); + full_keys.reserve(entries.size()); + values.reserve(entries.size()); - full_keys.emplace_back(key_pool.pool(properties->keys()[i]->string().c_str())); - values.push_back(std::move(sv)); + for (const auto &e : entries) { + if (e.key->type == JSON_STRING) { + serial_val sv = stringify_value(e.value, sst->fname, sst->line, feature); + + full_keys.emplace_back(key_pool.pool(e.key->string().c_str())); + values.push_back(std::move(sv)); + } } } diff --git a/jsonpull/jsonpull.cpp b/jsonpull/jsonpull.cpp index d35bf8d3..53704ae3 100644 --- a/jsonpull/jsonpull.cpp +++ b/jsonpull/jsonpull.cpp @@ -134,7 +134,7 @@ static json_object_ptr add_object(json_pull *j, json_type type) { } } else if (c->type == JSON_HASH) { if (c->expect == JSON_VALUE) { - c->values().back() = o; + c->entries().back().value = o; c->expect = JSON_COMMA; } else if (c->expect == JSON_KEY) { if (type != JSON_STRING) { @@ -142,8 +142,7 @@ static json_object_ptr add_object(json_pull *j, json_type type) { return nullptr; } - c->keys().push_back(o); - c->values().push_back(nullptr); + c->entries().push_back({o, nullptr}); c->expect = JSON_COLON; } else { j->error = "Expected a comma or colon"; @@ -164,14 +163,9 @@ json_object_ptr json_hash_get(json_object *o, const char *s) { return nullptr; } - const auto &keys = o->keys(); - const auto &vals = o->values(); - for (size_t i = 0; i < keys.size(); i++) { - const auto &key = keys[i]; - if (key != nullptr && key->type == JSON_STRING) { - if (key->string() == s) { - return vals[i]; - } + for (const auto &e : o->entries()) { + if (e.key != nullptr && e.key->type == JSON_STRING && e.key->string() == s) { + return e.value; } } @@ -299,7 +293,7 @@ again: } if (cc->expect != JSON_COMMA) { - if (!(cc->expect == JSON_KEY && cc->keys().size() == 0)) { + if (!(cc->expect == JSON_KEY && cc->entries().size() == 0)) { j->error = "Found } without final element"; return nullptr; } @@ -676,11 +670,9 @@ static void clear_back_pointers(json_object *o) { } if (o->type == JSON_HASH) { - const auto &keys = o->keys(); - const auto &vals = o->values(); - for (size_t i = 0; i < keys.size(); i++) { - clear_back_pointers(keys[i].get()); - clear_back_pointers(vals[i].get()); + for (const auto &e : o->entries()) { + clear_back_pointers(e.key.get()); + clear_back_pointers(e.value.get()); } } else if (o->type == JSON_ARRAY) { const auto &arr = o->array(); @@ -713,28 +705,26 @@ void json_disconnect(json_object_ptr o) { } } } else if (parent->type == JSON_HASH) { - auto &keys = parent->keys(); - auto &vals = parent->values(); + auto &entries = parent->entries(); - for (size_t i = 0; i < keys.size(); i++) { - if (keys[i].get() == o.get()) { + for (size_t i = 0; i < entries.size(); i++) { + auto &e = entries[i]; + if (e.key.get() == o.get()) { // Leave a NULL placeholder in the key slot so the // surrounding value isn't shifted; if the corresponding // value is also detached the pair is removed below. - keys[i] = fabricate_object(parent->parser, parent, JSON_NULL); + e.key = fabricate_object(parent->parser, parent, JSON_NULL); - if (vals[i] != nullptr && vals[i]->type == JSON_NULL && keys[i]->type == JSON_NULL) { - keys.erase(keys.begin() + i); - vals.erase(vals.begin() + i); + if (e.value != nullptr && e.value->type == JSON_NULL && e.key->type == JSON_NULL) { + entries.erase(entries.begin() + i); } break; } - if (vals[i].get() == o.get()) { - vals[i] = fabricate_object(parent->parser, parent, JSON_NULL); + if (e.value.get() == o.get()) { + e.value = fabricate_object(parent->parser, parent, JSON_NULL); - if (keys[i] != nullptr && keys[i]->type == JSON_NULL && vals[i]->type == JSON_NULL) { - keys.erase(keys.begin() + i); - vals.erase(vals.begin() + i); + if (e.key != nullptr && e.key->type == JSON_NULL && e.value->type == JSON_NULL) { + entries.erase(entries.begin() + i); } break; } @@ -815,13 +805,12 @@ static void json_print(std::string &val, json_object *o) { } else if (o->type == JSON_HASH) { string_append_c(val, '{'); - const auto &keys = o->keys(); - const auto &vals = o->values(); - for (size_t i = 0; i < keys.size(); i++) { - json_print(val, keys[i].get()); + const auto &entries = o->entries(); + for (size_t i = 0; i < entries.size(); i++) { + json_print(val, entries[i].key.get()); string_append_c(val, ':'); - json_print(val, vals[i].get()); - if (i + 1 < keys.size()) { + json_print(val, entries[i].value.get()); + if (i + 1 < entries.size()) { string_append_c(val, ','); } } diff --git a/jsonpull/jsonpull.h b/jsonpull/jsonpull.h index fa9b265f..b4ac51a6 100644 --- a/jsonpull/jsonpull.h +++ b/jsonpull/jsonpull.h @@ -34,6 +34,16 @@ struct json_pull; typedef std::shared_ptr json_object_ptr; typedef std::shared_ptr json_pull_ptr; +// A single key/value pair inside a JSON_HASH. The pairs are stored in +// insertion order in a single std::vector on json_hash, so +// callers can range-for over `o->entries()` with structured bindings +// (`for (auto &[k, v] : o->entries()) ...`) while still preserving the +// order keys appeared in the source document. +struct json_entry { + json_object_ptr key; + json_object_ptr value; +}; + // json_object is a small base type that just records the JSON type and // the back-pointers to its parent and parser. The actual value payload // lives in a type-specific subclass (json_number, json_string, json_array, @@ -83,10 +93,8 @@ struct json_object { inline std::vector &array(); inline const std::vector &array() const; - inline std::vector &keys(); - inline const std::vector &keys() const; - inline std::vector &values(); - inline const std::vector &values() const; + inline std::vector &entries(); + inline const std::vector &entries() const; }; struct json_number : json_object { @@ -113,8 +121,7 @@ struct json_array : json_object { }; struct json_hash : json_object { - std::vector keys_value; - std::vector values_value; + std::vector entries_value; json_hash() : json_object(JSON_HASH) {} json_hash(json_object *p, json_pull *pl) : json_object(JSON_HASH, p, pl) {} @@ -163,21 +170,13 @@ inline const std::vector &json_object::array() const { return static_cast(this)->array_value; } -inline std::vector &json_object::keys() { +inline std::vector &json_object::entries() { assert(type == JSON_HASH); - return static_cast(this)->keys_value; + return static_cast(this)->entries_value; } -inline const std::vector &json_object::keys() const { +inline const std::vector &json_object::entries() const { assert(type == JSON_HASH); - return static_cast(this)->keys_value; -} -inline std::vector &json_object::values() { - assert(type == JSON_HASH); - return static_cast(this)->values_value; -} -inline const std::vector &json_object::values() const { - assert(type == JSON_HASH); - return static_cast(this)->values_value; + return static_cast(this)->entries_value; } struct json_pull { diff --git a/jsontool.cpp b/jsontool.cpp index e83ab5a8..9a8dbfc3 100644 --- a/jsontool.cpp +++ b/jsontool.cpp @@ -298,8 +298,7 @@ void join_csv(json_object_ptr j) { } if (fields.size() > 0 && joinkey == fields[0]) { - properties->keys().reserve(properties->keys().size() + fields.size()); - properties->values().reserve(properties->values().size() + fields.size()); + properties->entries().reserve(properties->entries().size() + fields.size()); for (size_t i = 1; i < fields.size(); i++) { std::string k = header[i]; @@ -333,8 +332,7 @@ void join_csv(json_object_ptr j) { abort(); } - properties->keys().push_back(ko); - properties->values().push_back(vo); + properties->entries().push_back({ko, vo}); } } } diff --git a/main.cpp b/main.cpp index fae5b50e..95a4096b 100644 --- a/main.cpp +++ b/main.cpp @@ -2885,17 +2885,16 @@ void set_attribute_value(const char *arg) { exit(EXIT_JSON); } - for (size_t i = 0; i < o->keys().size(); i++) { - json_object_ptr k = o->keys()[i]; - json_object_ptr v = o->values()[i]; - - if (k->type != JSON_STRING) { + size_t i = 0; + for (const auto &e : o->entries()) { + if (e.key->type != JSON_STRING) { fprintf(stderr, "%s: --set-attribute %s: key %zu not a string\n", *av, arg, i); exit(EXIT_JSON); } - serial_val val = stringify_value(v, "json", 1, o); - set_attributes.emplace(k->string(), val); + serial_val val = stringify_value(e.value, "json", 1, o); + set_attributes.emplace(e.key->string(), val); + i++; } return; diff --git a/plugin.cpp b/plugin.cpp index 10e8d9c3..b9d5f2de 100644 --- a/plugin.cpp +++ b/plugin.cpp @@ -343,18 +343,20 @@ serial_feature parse_feature(json_pull_ptr jp, int z, unsigned x, unsigned y, st } } - for (size_t i = 0; i < properties->keys().size(); i++) { - serial_val v = stringify_value(properties->values()[i], "Filter output", jp->line, j); + if (properties->type == JSON_HASH) { + for (const auto &e : properties->entries()) { + serial_val v = stringify_value(e.value, "Filter output", jp->line, j); - // Nulls can be excluded here because the expression evaluation filter - // would have already run before prefiltering + // Nulls can be excluded here because the expression evaluation filter + // would have already run before prefiltering - if (v.type != mvt_null) { - sf.full_keys.push_back(key_pool.pool(properties->keys()[i]->string())); - sf.full_values.push_back(v); + if (v.type != mvt_null) { + sf.full_keys.push_back(key_pool.pool(e.key->string())); + sf.full_values.push_back(v); - if (!postfilter) { - add_to_tilestats(ts->second.tilestats, properties->keys()[i]->string(), v); + if (!postfilter) { + add_to_tilestats(ts->second.tilestats, e.key->string(), v); + } } } } diff --git a/pmtiles_file.cpp b/pmtiles_file.cpp index 39ed4f44..9a8c62a1 100644 --- a/pmtiles_file.cpp +++ b/pmtiles_file.cpp @@ -415,37 +415,37 @@ sqlite3 *pmtilesmeta2tmp(const char *fname, const char *pmtiles_map) { state.nospace = true; state.json_write_hash(); - for (size_t i = 0; i < o->keys().size(); i++) { - const std::string &key = o->keys()[i]->string(); - if (key == "vector_layers" && o->values()[i]->type == JSON_ARRAY) { + for (const auto &e : o->entries()) { + const std::string &key = e.key->string(); + if (key == "vector_layers" && e.value->type == JSON_ARRAY) { has_json = true; state.nospace = true; state.json_write_string("vector_layers"); state.nospace = true; - state.json_write_json(json_stringify(o->values()[i])); - } else if (key == "tilestats" && o->values()[i]->type == JSON_HASH) { + state.json_write_json(json_stringify(e.value)); + } else if (key == "tilestats" && e.value->type == JSON_HASH) { has_json = true; state.nospace = true; state.json_write_string("tilestats"); state.nospace = true; - state.json_write_json(json_stringify(o->values()[i])); - } else if (key == "strategies" && o->values()[i]->type == JSON_ARRAY) { - sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('strategies', %Q);", json_stringify(o->values()[i]).c_str()); + state.json_write_json(json_stringify(e.value)); + } else if (key == "strategies" && e.value->type == JSON_ARRAY) { + sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('strategies', %Q);", json_stringify(e.value).c_str()); if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) { fprintf(stderr, "set %s in metadata: %s\n", key.c_str(), err); } sqlite3_free(sql); - } else if (key == "tippecanoe_decisions" && o->values()[i]->type == JSON_HASH) { - sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('tippecanoe_decisions', %Q);", json_stringify(o->values()[i]).c_str()); + } else if (key == "tippecanoe_decisions" && e.value->type == JSON_HASH) { + sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('tippecanoe_decisions', %Q);", json_stringify(e.value).c_str()); if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) { fprintf(stderr, "set %s in metadata: %s\n", key.c_str(), err); } sqlite3_free(sql); - } else if (o->keys()[i]->type != JSON_STRING || o->values()[i]->type != JSON_STRING) { + } else if (e.key->type != JSON_STRING || e.value->type != JSON_STRING) { fprintf(stderr, "%s\n", key.c_str()); fprintf(stderr, "%s: non-string in metadata\n", fname); } else { - sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES (%Q, %Q);", key.c_str(), o->values()[i]->string().c_str()); + sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES (%Q, %Q);", key.c_str(), e.value->string().c_str()); if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) { fprintf(stderr, "set %s in metadata: %s\n", key.c_str(), err); } diff --git a/read_json.cpp b/read_json.cpp index 61304153..45a533f4 100644 --- a/read_json.cpp +++ b/read_json.cpp @@ -382,15 +382,17 @@ std::vector parse_layers(FILE *fp, int z, unsigned x, unsigned y, int feature.has_id = true; } - for (size_t i = 0; i < properties->keys().size(); i++) { - serial_val sv = stringify_value(properties->values()[i], "Filter output", jp->line, j); + if (properties->type == JSON_HASH) { + for (const auto &e : properties->entries()) { + serial_val sv = stringify_value(e.value, "Filter output", jp->line, j); - // Nulls can be excluded here because this is the postfilter - // and it is nearly time to create the vector representation + // Nulls can be excluded here because this is the postfilter + // and it is nearly time to create the vector representation - if (sv.type != mvt_null) { - mvt_value v = stringified_to_mvt_value(sv.type, sv.s.c_str(), tile_stringpool); - l->second.tag(feature, properties->keys()[i]->string(), v); + if (sv.type != mvt_null) { + mvt_value v = stringified_to_mvt_value(sv.type, sv.s.c_str(), tile_stringpool); + l->second.tag(feature, e.key->string(), v); + } } } diff --git a/tile-join.cpp b/tile-join.cpp index cf4eb833..5f7af368 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -972,40 +972,39 @@ void handle_strategies(const unsigned char *s, std::vector *st) { for (size_t i = 0; i < o->array().size(); i++) { json_object_ptr h = o->array()[i]; if (h->type == JSON_HASH) { - for (size_t j = 0; j < h->keys().size(); j++) { - json_object_ptr k = h->keys()[j]; - json_object_ptr v = h->values()[j]; - - if (k->type != JSON_STRING) { + size_t j = 0; + for (const auto &kv : h->entries()) { + if (kv.key->type != JSON_STRING) { fprintf(stderr, "Key %zu of %zu is not a string: %s\n", j, i, s); - } else if (v->type != JSON_NUMBER) { + } else if (kv.value->type != JSON_NUMBER) { fprintf(stderr, "Value %zu of %zu is not a number: %s\n", j, i, s); } else { if (i >= st->size()) { st->resize(i + 1); } - const std::string &key = k->string(); + const std::string &key = kv.key->string(); if (key == "dropped_by_rate") { - (*st)[i].dropped_by_rate += v->number(); + (*st)[i].dropped_by_rate += kv.value->number(); } else if (key == "dropped_by_gamma") { - (*st)[i].dropped_by_gamma += v->number(); + (*st)[i].dropped_by_gamma += kv.value->number(); } else if (key == "dropped_as_needed") { - (*st)[i].dropped_as_needed += v->number(); + (*st)[i].dropped_as_needed += kv.value->number(); } else if (key == "coalesced_as_needed") { - (*st)[i].coalesced_as_needed += v->number(); + (*st)[i].coalesced_as_needed += kv.value->number(); } else if (key == "truncated_zooms") { - (*st)[i].truncated_zooms += v->number(); + (*st)[i].truncated_zooms += kv.value->number(); } else if (key == "detail_reduced") { - (*st)[i].detail_reduced += v->number(); + (*st)[i].detail_reduced += kv.value->number(); } else if (key == "tiny_polygons") { - (*st)[i].tiny_polygons += v->number(); + (*st)[i].tiny_polygons += kv.value->number(); } else if (key == "tile_size_desired") { - (*st)[i].tile_size += v->number(); + (*st)[i].tile_size += kv.value->number(); } else if (key == "feature_count_desired") { - (*st)[i].feature_count += v->number(); + (*st)[i].feature_count += kv.value->number(); } } + j++; } } else { fprintf(stderr, "Element %zu is not a hash: %s\n", i, s); @@ -1035,15 +1034,15 @@ void handle_vector_layers(json_object_ptr vector_layers, std::maparray()[i], "fields"); if (fields != nullptr && fields->type == JSON_HASH) { - for (size_t j = 0; j < fields->keys().size(); j++) { - if (fields->keys()[j]->type == JSON_STRING && fields->values()[j]->type) { - const std::string &desc2 = fields->values()[j]->string(); + for (const auto &e : fields->entries()) { + if (e.key->type == JSON_STRING && e.value->type) { + const std::string &desc2 = e.value->string(); if (desc2 != "Number" && desc2 != "String" && desc2 != "Boolean" && desc2 != "Mixed") { - attribute_descriptions.insert(std::pair(fields->keys()[j]->string(), desc2)); + attribute_descriptions.insert(std::pair(e.key->string(), desc2)); } } }