Subclass json_object so primitives shrink from 168 to 24 bytes

The previous "every member in a struct" layout cost 168 bytes per
json_object, even for JSON_NULL / JSON_TRUE / JSON_FALSE nodes that
have no payload. Splitting json_object into a small base class plus
json_number / json_string / json_array / json_hash subclasses brings
each instance down to just the size of its actual contents:

  json_object (base, TRUE / FALSE / NULL)   24 bytes
  json_number                                48 bytes
  json_string                                48 bytes
  json_array  (empty)                        48 bytes
  json_hash   (empty)                        72 bytes

Other size wins along the way:

* Drop enable_shared_from_this<json_object> (its embedded weak_ptr
  was 16 bytes per node). json_pull now keeps an explicit
  container_stack and the parser no longer needs to resurrect a
  shared_ptr from a raw `parent` walk.
* Remove the unused `refcon` slot from the string variant.
* No virtual destructor: shared_ptr keeps the deleter from the
  original std::make_shared<json_xxx> call, so destroying a
  shared_ptr<json_object> still runs the right subclass dtor.

The base class exposes type-tagged accessors (o->string(),
o->number(), o->array(), o->keys(), o->values(), o->large_signed(),
o->large_unsigned()) that assert the type matches and downcast to
the appropriate subclass storage. All call sites were swept from
the old `o->value.X.Y` field paths to these accessors. A raw-pointer
overload of json_hash_get() replaces the few external uses of
shared_from_this() that survived in geojson-loop.cpp.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Erica Fischer
2026-05-30 17:49:08 -07:00
co-authored by Cursor
parent 3da03c6075
commit f366b2c4aa
14 changed files with 424 additions and 291 deletions
+16 -22
View File
@@ -149,7 +149,7 @@ void out(std::string const &s, int type, json_object_ptr properties) {
if (o != nullptr) {
found = true;
if (o->type == JSON_STRING || o->type == JSON_NUMBER) {
extracted = sort_quote(o->value.string.string.c_str());
extracted = sort_quote(o->string().c_str());
} else {
extracted = sort_quote(json_stringify(o).c_str());
}
@@ -244,9 +244,9 @@ void join_csv(json_object_ptr j) {
std::string joinkey;
if (key->type == JSON_STRING) {
joinkey = key->value.string.string;
joinkey = key->string();
} else if (key->type == JSON_NUMBER) {
joinkey = milo::dtoa_milo(key->value.number.number);
joinkey = milo::dtoa_milo(key->number());
} else {
joinkey = json_stringify(key);
}
@@ -298,8 +298,8 @@ void join_csv(json_object_ptr j) {
}
if (fields.size() > 0 && joinkey == fields[0]) {
properties->value.object.keys.reserve(properties->value.object.keys.size() + fields.size());
properties->value.object.values.reserve(properties->value.object.values.size() + fields.size());
properties->keys().reserve(properties->keys().size() + fields.size());
properties->values().reserve(properties->values().size() + fields.size());
for (size_t i = 1; i < fields.size(); i++) {
std::string k = header[i];
@@ -317,30 +317,24 @@ void join_csv(json_object_ptr j) {
}
if (attr_type != JSON_NULL) {
auto ko = std::make_shared<json_object>();
auto vo = std::make_shared<json_object>();
ko->type = JSON_STRING;
ko->parent = properties.get();
ko->parser = properties->parser;
ko->value.string.string = k;
vo->type = attr_type;
vo->parent = properties.get();
vo->parser = properties->parser;
auto ko = std::make_shared<json_string>(properties.get(), properties->parser);
ko->string_value = k;
json_object_ptr vo;
if (attr_type == JSON_STRING) {
vo->value.string.string = v;
auto s = std::make_shared<json_string>(properties.get(), properties->parser);
s->string_value = v;
vo = s;
} else if (attr_type == JSON_NUMBER) {
vo->value.number.number = atof(v.c_str());
vo->value.number.large_unsigned = 0;
vo->value.number.large_signed = 0;
auto n = std::make_shared<json_number>(properties.get(), properties->parser);
n->number_value = atof(v.c_str());
vo = n;
} else {
abort();
}
properties->value.object.keys.push_back(ko);
properties->value.object.values.push_back(vo);
properties->keys().push_back(ko);
properties->values().push_back(vo);
}
}
}