#include <meta.h>
#include <stdio.h>
#include <limits.h>
#include <math.h>

void SerializeIterToJson_serialize_1_comps_empty(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_f32_t)},
            {"y", ecs_id(ecs_f32_t)}
        }
    });

    ecs_query_t *q = ecs_query_new(world, "Position");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);
    test_assert(json != NULL);
    test_str(json, "{\"ids\":[[\"Position\"]], \"results\":[]}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_1_comps_2_ents_same_table(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e2, Position, {30, 40});

    ecs_query_t *q = ecs_query_new(world, "Position");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);
    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[["
                "{\"x\":10, \"y\":20}, "
                "{\"x\":30, \"y\":40}"
            "]]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_2_comps_2_ents_same_table(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Mass);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Mass),
        .members = {
            {"value", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e2, Position, {30, 40});

    ecs_set(world, e1, Mass, {1});
    ecs_set(world, e2, Mass, {2});

    ecs_query_t *q = ecs_query_new(world, "Position, Mass");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);
    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"Mass\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"Mass\"]], "
            "\"sources\":[0, 0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[["
                "{\"x\":10, \"y\":20}, "
                "{\"x\":30, \"y\":40}"
            "], ["
                "{\"value\":1}, "
                "{\"value\":2}"
            "]]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_1_tag_2_ents_same_table(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, MyTag);

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_add(world, e1, MyTag);
    ecs_add(world, e2, MyTag);

    ecs_query_t *q = ecs_query_new(world, "MyTag");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);
    test_str(json, 
    "{"
        "\"ids\":[[\"MyTag\"]], "
        "\"results\":[{"
            "\"ids\":[[\"MyTag\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_1_tag_1_comp_2_ents_same_table(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, MyTag);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e2, Position, {30, 40});

    ecs_add(world, e1, MyTag);
    ecs_add(world, e2, MyTag);

    ecs_query_t *q = ecs_query_new(world, "Position, MyTag");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);
    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"MyTag\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"MyTag\"]], "
            "\"sources\":[0, 0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[["
                "{\"x\":10, \"y\":20}, "
                "{\"x\":30, \"y\":40}"
            "], 0]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_1_tag_1_comp_4_ents_two_tables(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");
    ecs_entity_t e3 = ecs_new_entity(world, "Hello");
    ecs_entity_t e4 = ecs_new_entity(world, "World");

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e2, Position, {30, 40});
    ecs_set(world, e3, Position, {50, 60});
    ecs_set(world, e4, Position, {70, 80});

    ecs_add(world, e1, TagA);
    ecs_add(world, e2, TagA);

    ecs_add(world, e3, TagA);
    ecs_add(world, e4, TagA);

    ecs_add(world, e3, TagB);
    ecs_add(world, e4, TagB);

    ecs_query_t *q = ecs_query_new(world, "Position, TagA");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);
    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"TagA\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"TagA\"]], "
            "\"sources\":[0, 0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[["
                "{\"x\":10, \"y\":20}, "
                "{\"x\":30, \"y\":40}"
            "], 0]"
        "}, {"
            "\"ids\":[[\"Position\"], [\"TagA\"]], "
            "\"sources\":[0, 0], "
            "\"entities\":["
                "\"Hello\", \"World\""
            "], "
            "\"values\":[["
                "{\"x\":50, \"y\":60}, "
                "{\"x\":70, \"y\":80}"
            "], 0]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_2_comps_1_owned_2_ents(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Mass);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Mass),
        .members = {
            {"value", ecs_id(ecs_i32_t)},
        }
    });

    ecs_entity_t base = ecs_new_entity(world, "Base");
    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_set(world, base, Mass, {100});
    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e2, Position, {30, 40});

    ecs_add_pair(world, e1, EcsIsA, base);
    ecs_add_pair(world, e2, EcsIsA, base);

    ecs_query_t *q = ecs_query_new(world, "Position, Mass(up)");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);
    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"Mass\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"Mass\"]], "
            "\"sources\":[0, \"Base\"], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[["
                "{\"x\":10, \"y\":20}, "
                "{\"x\":30, \"y\":40}"
            "], {"
                "\"value\":100"
            "}]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_pair_wildcard(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Rel);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_add_pair(world, e1, Rel, ObjA);
    ecs_add_pair(world, e2, Rel, ObjB);

    ecs_query_t *q = ecs_query_new(world, "(Rel, *)");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);
    test_str(json, 
    "{"
        "\"ids\":[[\"Rel\",\"*\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Rel\",\"ObjA\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"Foo\""
            "]"
        "}, {"
            "\"ids\":[[\"Rel\",\"ObjB\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"Bar\""
            "]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_var(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Rel);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_add_pair(world, e1, Rel, ObjA);
    ecs_add_pair(world, e2, Rel, ObjB);

    ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){
        .expr = "(Rel, $X)"
    });

    ecs_iter_t it = ecs_rule_iter(world, r);

    char *json = ecs_iter_to_json(world, &it, NULL);
    test_str(json, 
    "{"
        "\"ids\":[[\"Rel\",\"*\"]], "
        "\"vars\":[\"X\"], "
        "\"results\":[{"
            "\"ids\":[[\"Rel\",\"ObjA\"]], "
            "\"sources\":[0], "
            "\"vars\":[\"ObjA\"], "
            "\"entities\":["
                "\"Foo\""
            "]"
        "}, {"
            "\"ids\":[[\"Rel\",\"ObjB\"]], "
            "\"sources\":[0], "
            "\"vars\":[\"ObjB\"], "
            "\"entities\":["
                "\"Bar\""
            "]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_2_vars(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, RelX);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ECS_TAG(world, RelY);
    ECS_TAG(world, ObjC);
    ECS_TAG(world, ObjD);

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_add_pair(world, e1, RelX, ObjA);
    ecs_add_pair(world, e2, RelX, ObjB);

    ecs_add_pair(world, e1, RelY, ObjC);
    ecs_add_pair(world, e2, RelY, ObjD);

    ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){
        .expr = "(RelX, $X), (RelY, $Y)"
    });

    ecs_iter_t it = ecs_rule_iter(world, r);

    char *json = ecs_iter_to_json(world, &it, NULL);
    test_str(json, 
    "{"
        "\"ids\":[[\"RelX\",\"*\"], [\"RelY\",\"*\"]], "
        "\"vars\":[\"X\", \"Y\"], "
        "\"results\":[{"
            "\"ids\":[[\"RelX\",\"ObjA\"], [\"RelY\",\"ObjC\"]], "
            "\"sources\":[0, 0], "
            "\"vars\":[\"ObjA\", \"ObjC\"], "
            "\"entities\":["
                "\"Foo\""
            "]"
        "}, {"
            "\"ids\":[[\"RelX\",\"ObjB\"], [\"RelY\",\"ObjD\"]], "
            "\"sources\":[0, 0], "
            "\"vars\":[\"ObjB\", \"ObjD\"], "
            "\"entities\":["
                "\"Bar\""
            "]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_type_info_1_tags(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, TagA);

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_add(world, e1, TagA);
    ecs_add(world, e2, TagA);

    ecs_query_t *q = ecs_query_new(world, "TagA");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_type_info = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"TagA\"]], "
        "\"results\":[{"
            "\"ids\":[[\"TagA\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_type_info_2_tags(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_add(world, e1, TagA);
    ecs_add(world, e1, TagB);
    ecs_add(world, e2, TagA);
    ecs_add(world, e2, TagB);

    ecs_query_t *q = ecs_query_new(world, "TagA, TagB");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_type_info = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"TagA\"], [\"TagB\"]], "
        "\"results\":[{"
            "\"ids\":[[\"TagA\"], [\"TagB\"]], "
            "\"sources\":[0, 0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_type_info_1_component(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_add(world, e1, Position);
    ecs_add(world, e2, Position);

    ecs_query_t *q = ecs_query_new(world, "Position");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_type_info = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"]], "
        "\"type_info\":{\"Position\":0}, "
        "\"results\":[{"
            "\"ids\":[[\"Position\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[0]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_type_info_2_components(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_add(world, e1, Position);
    ecs_add(world, e2, Position);

    ecs_add(world, e1, Velocity);
    ecs_add(world, e2, Velocity);

    ecs_query_t *q = ecs_query_new(world, "Position, Velocity");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_type_info = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"Velocity\"]], "
        "\"type_info\":{\"Position\":0, \"Velocity\":0}, "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"Velocity\"]], "
            "\"sources\":[0, 0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[0, 0]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_type_info_1_struct(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);

    ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_entity(world, {.name = "Position"}),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    test_assert(t == ecs_id(Position));

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_add(world, e1, Position);
    ecs_add(world, e2, Position);

    ecs_query_t *q = ecs_query_new(world, "Position");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_type_info = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"]], "
        "\"type_info\":{\"Position\":{\"x\":[\"int\"], \"y\":[\"int\"]}}, "
        "\"results\":[{"
            "\"ids\":[[\"Position\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[["
                "{\"x\":0, \"y\":0}, "
                "{\"x\":0, \"y\":0}"
            "]]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_type_info_1_component_1_struct(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_entity(world, {.name = "Position"}),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    test_assert(t == ecs_id(Position));

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_add(world, e1, Position);
    ecs_add(world, e2, Position);

    ecs_add(world, e1, Velocity);
    ecs_add(world, e2, Velocity);

    ecs_query_t *q = ecs_query_new(world, "Position, Velocity");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_type_info = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"Velocity\"]], "
        "\"type_info\":{"
            "\"Position\":{\"x\":[\"int\"], \"y\":[\"int\"]}, "
            "\"Velocity\":0"
        "}, "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"Velocity\"]], "
            "\"sources\":[0, 0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[["
                "{\"x\":0, \"y\":0}, "
                "{\"x\":0, \"y\":0}"
            "], 0]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_type_info_2_structs(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_entity(world, {.name = "Position"}),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });
    test_assert(t == ecs_id(Position));

    t = ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_entity(world, {.name = "Velocity"}),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });
    test_assert(t == ecs_id(Velocity));

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_add(world, e1, Position);
    ecs_add(world, e2, Position);

    ecs_add(world, e1, Velocity);
    ecs_add(world, e2, Velocity);

    ecs_query_t *q = ecs_query_new(world, "Position, Velocity");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_type_info = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"Velocity\"]], "
        "\"type_info\":{"
            "\"Position\":{\"x\":[\"int\"], \"y\":[\"int\"]}, "
            "\"Velocity\":{\"x\":[\"int\"], \"y\":[\"int\"]}"
        "}, "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"Velocity\"]], "
            "\"sources\":[0, 0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[["
                "{\"x\":0, \"y\":0}, "
                "{\"x\":0, \"y\":0}"
            "], ["
                "{\"x\":0, \"y\":0}, "
                "{\"x\":0, \"y\":0}"
            "]]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_type_info_w_unit(void) {
    typedef struct {
        ecs_i32_t value;    
    } T;

    ecs_world_t *world = ecs_init();

    ecs_entity_t u = ecs_unit_init(world, &(ecs_unit_desc_t){
        .entity = ecs_entity(world, {.name = "celsius"}),
        .symbol = "°"
    });
    test_assert(u != 0);

    ecs_entity_t ecs_id(T) = ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_entity(world, {.name = "T"}),
        .members = {
            {"value", ecs_id(ecs_i32_t), .unit = u}
        }
    });
    test_assert(ecs_id(T) != 0);

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_set(world, e1, T, {24});
    ecs_set(world, e2, T, {16});

    ecs_query_t *q = ecs_query_new(world, "T");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_type_info = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"T\"]], "
        "\"type_info\":{"
            "\"T\":{\"value\":[\"int\", {"
                "\"unit\":\"celsius\", \"symbol\":\"°\"}]"
            "}"
        "}, "
        "\"results\":[{"
            "\"ids\":[[\"T\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[["
                "{\"value\":24}, "
                "{\"value\":16}"
            "]]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_type_info_w_unit_quantity(void) {
    typedef struct {
        ecs_i32_t value;    
    } T;

    ecs_world_t *world = ecs_init();

    ecs_entity_t qt = ecs_quantity_init(world, &(ecs_entity_desc_t){
        .name = "temperature"
    });
    test_assert(qt != 0);

    ecs_entity_t u = ecs_unit_init(world, &(ecs_unit_desc_t){
        .entity = ecs_entity(world, {.name = "celsius"}),
        .symbol = "°",
        .quantity = qt
    });
    test_assert(u != 0);

    ecs_entity_t ecs_id(T) = ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_entity(world, {.name = "T"}),
        .members = {
            {"value", ecs_id(ecs_i32_t), .unit = u}
        }
    });
    test_assert(ecs_id(T) != 0);

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_set(world, e1, T, {24});
    ecs_set(world, e2, T, {16});

    ecs_query_t *q = ecs_query_new(world, "T");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_type_info = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"T\"]], "
        "\"type_info\":{"
            "\"T\":{\"value\":[\"int\", {"
                "\"unit\":\"celsius\", \"symbol\":\"°\", \"quantity\":\"temperature\"}]"
            "}"
        "}, "
        "\"results\":[{"
            "\"ids\":[[\"T\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[["
                "{\"value\":24}, "
                "{\"value\":16}"
            "]]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_type_info_w_unit_over(void) {
    typedef struct {
        ecs_i32_t value;    
    } T;

    ecs_world_t *world = ecs_init();

    ecs_entity_t u_1 = ecs_unit_init(world, &(ecs_unit_desc_t){
        .entity = ecs_entity(world, {.name = "seconds"}),
        .symbol = "s"
    });
    test_assert(u_1 != 0);

    ecs_entity_t u_2 = ecs_unit_init(world, &(ecs_unit_desc_t){
        .entity = ecs_entity(world, {.name = "meters"}),
        .symbol = "m"
    });
    test_assert(u_2 != 0);

    ecs_entity_t u_3 = ecs_unit_init(world, &(ecs_unit_desc_t){
        .entity = ecs_entity(world, {.name = "meters_per_second"}),
        .base = u_2,
        .over = u_1
    });
    test_assert(u_3 != 0);

    ecs_entity_t ecs_id(T) = ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_entity(world, {.name = "T"}),
        .members = {
            {"value", ecs_id(ecs_i32_t), .unit = u_3}
        }
    });
    test_assert(ecs_id(T) != 0);

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_set(world, e1, T, {24});
    ecs_set(world, e2, T, {16});

    ecs_query_t *q = ecs_query_new(world, "T");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_type_info = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"T\"]], "
        "\"type_info\":{"
            "\"T\":{\"value\":[\"int\", {"
                "\"unit\":\"meters_per_second\", \"symbol\":\"m/s\"}]"
            "}"
        "}, "
        "\"results\":[{"
            "\"ids\":[[\"T\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"Foo\", \"Bar\""
            "], "
            "\"values\":[["
                "{\"value\":24}, "
                "{\"value\":16}"
            "]]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_entity_label(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Tag);

    ecs_entity_t e1 = ecs_new_entity(world, "foo_bar");
    ecs_entity_t e2 = ecs_new_entity(world, "hello_world");
    ecs_doc_set_name(world, e2, "Hello World");

    ecs_add(world, e1, Tag);
    ecs_add(world, e2, Tag);

    ecs_query_t *q = ecs_query_new(world, "Tag");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_entity_labels = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"Tag\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Tag\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"foo_bar\""
            "]"
        "}, {"
            "\"ids\":[[\"Tag\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"hello_world\""
            "], "
            "\"entity_labels\":["
                "\"Hello World\""
            "]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_var_labels(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Rel);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_entity_t e2 = ecs_new_entity(world, "Bar");

    ecs_add_pair(world, e1, Rel, ObjA);
    ecs_add_pair(world, e2, Rel, ObjB);

    ecs_doc_set_name(world, ObjA, "Object A");

    ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){
        .expr = "(Rel, $X)"
    });

    ecs_iter_t it = ecs_rule_iter(world, r);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_variable_labels = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"Rel\",\"*\"]], "
        "\"vars\":[\"X\"], "
        "\"results\":[{"
            "\"ids\":[[\"Rel\",\"ObjA\"]], "
            "\"sources\":[0], "
            "\"vars\":[\"ObjA\"], "
            "\"var_labels\":[\"Object A\"], "
            "\"entities\":["
                "\"Foo\""
            "]"
        "}, {"
            "\"ids\":[[\"Rel\",\"ObjB\"]], "
            "\"sources\":[0], "
            "\"vars\":[\"ObjB\"], "
            "\"var_labels\":[\"ObjB\"], "
            "\"entities\":["
                "\"Bar\""
            "]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_color(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Tag);

    ecs_entity_t e1 = ecs_new_entity(world, "foo_bar");
    ecs_entity_t e2 = ecs_new_entity(world, "hello_world");
    ecs_doc_set_color(world, e2, "#47B576");

    ecs_add(world, e1, Tag);
    ecs_add(world, e2, Tag);

    ecs_query_t *q = ecs_query_new(world, "Tag");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_colors = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
    "{"
        "\"ids\":[[\"Tag\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Tag\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"foo_bar\""
            "]"
        "}, {"
            "\"ids\":[[\"Tag\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"hello_world\""
            "], "
            "\"colors\":["
                "\"#47B576\""
            "]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_var_component(void) {
    typedef struct {
        ecs_i32_t x;
    } T;

    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Rel);
    ECS_TAG(world, Obj);

    ecs_entity_t ecs_id(T) = ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_entity(world, {.name = "T"}),
        .members = {
            {"x", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e = ecs_new_entity(world, "Foo");
    ecs_add_pair(world, e, Rel, Obj);
    ecs_set(world, Obj, T, {10});

    ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){
        .expr = "(Rel, $X), T($X)"
    });

    ecs_iter_t it = ecs_rule_iter(world, r);

    char *json = ecs_iter_to_json(world, &it, NULL);

    test_str(json, 
    "{"
        "\"ids\":[[\"Rel\",\"*\"], [\"T\"]], "
        "\"vars\":[\"X\"], "
        "\"results\":[{"
            "\"ids\":[[\"Rel\",\"Obj\"], [\"T\"]], "
            "\"sources\":[0, \"Obj\"], "
            "\"vars\":[\"Obj\"], "
            "\"entities\":["
                "\"Foo\""
            "], "
            "\"values\":[0, {\"x\":10}]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_optional_tag(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, TagA);
    ECS_TAG(world, TagB);

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_entity_t e2 = ecs_new_entity(world, "e2");

    ecs_add(world, e1, TagB);
    ecs_add(world, e1, TagA);
    ecs_add(world, e2, TagA);

    ecs_query_t *q = ecs_query_new(world, "TagA, ?TagB");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);

    test_str(json, 
    "{"
        "\"ids\":[[\"TagA\"], [\"TagB\"]], "
        "\"results\":[{"
            "\"ids\":[[\"TagA\"], [\"TagB\"]], "
            "\"sources\":[0, 0], "
            "\"is_set\":[true, true], "
            "\"entities\":["
                "\"e1\""
            "]"
        "}, {"
            "\"ids\":[[\"TagA\"], [\"TagB\"]], "
            "\"sources\":[0, 0], "
            "\"is_set\":[true, false], "
            "\"entities\":["
                "\"e2\""
            "]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_optional_component(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_entity_t e2 = ecs_new_entity(world, "e2");

    ecs_add(world, e1, Velocity);
    ecs_add(world, e1, Position);
    ecs_add(world, e2, Position);

    ecs_query_t *q = ecs_query_new(world, "Position, ?Velocity");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);

    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"Velocity\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"Velocity\"]], "
            "\"sources\":[0, 0], "
            "\"is_set\":[true, true], "
            "\"entities\":["
                "\"e1\""
            "], "
            "\"values\":[0, 0]"
        "}, {"
            "\"ids\":[[\"Position\"], [\"Velocity\"]], "
            "\"sources\":[0, 0], "
            "\"is_set\":[true, false], "
            "\"entities\":["
                "\"e2\""
            "], "
            "\"values\":[0, 0]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_optional_reflected_component(void) {
    ecs_world_t *world = ecs_init();

    ecs_entity_t ecs_id(Position) = ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_entity(world, {.name = "Position"}),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t ecs_id(Velocity) = ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_entity(world, {.name = "Velocity"}),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_entity_t e2 = ecs_new_entity(world, "e2");

    ecs_set(world, e1, Velocity, {1, 2});
    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e2, Position, {10, 20});

    ecs_query_t *q = ecs_query_new(world, "Position, ?Velocity");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);

    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"Velocity\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"Velocity\"]], "
            "\"sources\":[0, 0], "
            "\"is_set\":[true, true], "
            "\"entities\":["
                "\"e1\""
            "], "
            "\"values\":[[{\"x\":10, \"y\":20}], [{\"x\":1, \"y\":2}]]"
        "}, {"
            "\"ids\":[[\"Position\"], [\"Velocity\"]], "
            "\"sources\":[0, 0], "
            "\"is_set\":[true, false], "
            "\"entities\":["
                "\"e2\""
            "], "
            "\"values\":[[{\"x\":10, \"y\":20}], []]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_inout_filter_tag(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, TagA);

    ecs_entity_t e1 = ecs_new_entity(world, "e1");

    ecs_add(world, e1, TagA);

    ecs_query_t *q = ecs_query_new(world, "[none] TagA");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);

    test_str(json, 
    "{"
        "\"ids\":[[\"TagA\"]], "
        "\"results\":[{"
            "\"ids\":[[\"TagA\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"e1\""
            "]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_inout_filter_component(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Velocity),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "e1");

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e1, Velocity, {1, 2});

    ecs_query_t *q = ecs_query_new(world, "[none] Position, Velocity");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);

    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"Velocity\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"Velocity\"]], "
            "\"sources\":[0, 0], "
            "\"entities\":["
                "\"e1\""
            "], "
            "\"values\":[0, [{\"x\":1, \"y\":2}]]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_inout_filter_reflected_component(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Velocity),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "e1");

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e1, Velocity, {1, 2});

    ecs_query_t *q = ecs_query_new(world, "[none] Position, Velocity");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);

    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"Velocity\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"Velocity\"]], "
            "\"sources\":[0, 0], "
            "\"entities\":["
                "\"e1\""
            "], "
            "\"values\":[0, [{\"x\":1, \"y\":2}]]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_inout_out_tag(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, TagA);

    ecs_entity_t e1 = ecs_new_entity(world, "e1");

    ecs_add(world, e1, TagA);

    ecs_query_t *q = ecs_query_new(world, "[out] TagA");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);

    test_str(json, 
    "{"
        "\"ids\":[[\"TagA\"]], "
        "\"results\":[{"
            "\"ids\":[[\"TagA\"]], "
            "\"sources\":[0], "
            "\"entities\":["
                "\"e1\""
            "]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_inout_out_component(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Velocity),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "e1");

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e1, Velocity, {1, 2});

    ecs_query_t *q = ecs_query_new(world, "[out] Position, Velocity");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);

    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"Velocity\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"Velocity\"]], "
            "\"sources\":[0, 0], "
            "\"entities\":["
                "\"e1\""
            "], "
            "\"values\":[0, [{\"x\":1, \"y\":2}]]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_inout_out_reflected_component(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Velocity),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "e1");

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e1, Velocity, {1, 2});

    ecs_query_t *q = ecs_query_new(world, "[out] Position, Velocity");
    ecs_iter_t it = ecs_query_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);

    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"], [\"Velocity\"]], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"], [\"Velocity\"]], "
            "\"sources\":[0, 0], "
            "\"entities\":["
                "\"e1\""
            "], "
            "\"values\":[0, [{\"x\":1, \"y\":2}]]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_component_from_var(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "e1");

    ecs_set(world, e1, Position, {10, 20});

    ecs_rule_t *r = ecs_rule_new(world, "Position($E)");
    ecs_iter_t it = ecs_rule_iter(world, r);

    char *json = ecs_iter_to_json(world, &it, NULL);

    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"]], "
        "\"vars\":[\"E\"], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"]], "
            "\"sources\":[\"e1\"], "
            "\"vars\":[\"e1\"], "
            "\"values\":[{\"x\":10, \"y\":20}]"
        "}]"
    "}");

    ecs_os_free(json);

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_ids(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Tag);

    ecs_query_t *q = ecs_query_new(world, "Tag");

    ecs_entity_t e = ecs_new_entity(world, "e");
    ecs_add(world, e, Tag);

    ecs_iter_t it = ecs_query_iter(world, q);
    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_ids = false;
    desc.serialize_entity_ids = true;
    desc.serialize_values = false;
    desc.serialize_is_set = false;
    desc.serialize_sources = false;
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);

    char *expect = ecs_asprintf(
        "{\"ids\":[[\"Tag\"]], \"results\":[{\"entities\":[\"e\"], \"entity_ids\":[%u]}]}",
        (uint32_t)e);
    test_str(json, expect);

    ecs_os_free(json);
    ecs_os_free(expect);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_ids_2_entities(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Tag);

    ecs_query_t *q = ecs_query_new(world, "Tag");

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_add(world, e1, Tag);

    ecs_entity_t e2 = ecs_new_entity(world, "e2");
    ecs_add(world, e2, Tag);

    ecs_iter_t it = ecs_query_iter(world, q);
    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_ids = false;
    desc.serialize_entity_ids = true;
    desc.serialize_values = false;
    desc.serialize_is_set = false;
    desc.serialize_sources = false;
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);

    char *expect = ecs_asprintf(
        "{\"ids\":[[\"Tag\"]], \"results\":[{\"entities\":[\"e1\", \"e2\"], \"entity_ids\":[%u, %u]}]}",
        (uint32_t)e1, (uint32_t)e2);
    test_str(json, expect);

    ecs_os_free(json);
    ecs_os_free(expect);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_variable_ids(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Tag);

    ecs_rule_t *q = ecs_rule_new(world, "Tag($Entity)");

    ecs_entity_t e = ecs_new_entity(world, "e");
    ecs_add(world, e, Tag);

    ecs_iter_t it = ecs_rule_iter(world, q);
    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_ids = false;
    desc.serialize_variable_ids = true;
    desc.serialize_values = false;
    desc.serialize_is_set = false;
    desc.serialize_sources = false;
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);

    char *expect = ecs_asprintf(
        "{\"ids\":[[\"Tag\"]], \"vars\":[\"Entity\"], \"results\":[{\"vars\":[\"e\"], \"var_ids\":[%u]}]}",
        (uint32_t)e);
    test_str(json, expect);

    ecs_os_free(json);
    ecs_os_free(expect);

    ecs_rule_fini(q);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_variable_ids_2_entities(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Tag);

    ecs_rule_t *q = ecs_rule_new(world, "Tag($Entity)");

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_add(world, e1, Tag);

    ecs_entity_t e2 = ecs_new_entity(world, "e2");
    ecs_add(world, e2, Tag);

    ecs_iter_t it = ecs_rule_iter(world, q);
    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_ids = false;
    desc.serialize_variable_ids = true;
    desc.serialize_values = false;
    desc.serialize_is_set = false;
    desc.serialize_sources = false;
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);

    char *expect = ecs_asprintf(
        "{\"ids\":[[\"Tag\"]], \"vars\":[\"Entity\"], \"results\":[{\"vars\":[\"e1\"], \"var_ids\":[%u]}, {\"vars\":[\"e2\"], \"var_ids\":[%u]}]}",
        (uint32_t)e1, (uint32_t)e2);
    test_str(json, expect);

    ecs_os_free(json);
    ecs_os_free(expect);

    ecs_rule_fini(q);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_invalid_value(void) {
    typedef enum {
        Red, Blue, Green
    } E;

    typedef struct {
        E x;
    } T;

    ecs_world_t *world = ecs_init();

    ecs_entity_t e = ecs_enum_init(world, &(ecs_enum_desc_t){
        .constants = {
            {"Red"}, {"Blue"}, {"Green"}
        }
    });

    ecs_entity_t ecs_id(T) = ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_entity(world, {.name = "T"}),
        .members = {
            {"x", e}
        }
    });

    for (int i = 0; i < 10; i ++) {
        ecs_entity_t p = ecs_new_id(world);
        ecs_entity_t e = ecs_new_w_pair(world, EcsChildOf, p);
        ecs_set(world, e, T, {4});
    }

    ecs_query_t *q = ecs_query_new(world, "T");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_log_set_level(-4);
    char *json = ecs_iter_to_json(world, &it, NULL);
    test_assert(json == NULL);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_recycled_pair_id(void) {
    ecs_world_t *world = ecs_init();

    ecs_entity_t rel = ecs_new_entity(world, "Rel");
    ecs_entity_t tgt = ecs_new_entity(world, "Tgt");
    ecs_delete(world, tgt);
    tgt = ecs_new_entity(world, "Tgt");

    ecs_entity_t e = ecs_new_w_pair(world, rel, tgt);
    ecs_set_name(world, e, "ent");

    ecs_query_t *q = ecs_query(world, {
        .filter.terms[0] = {
            .first.id = rel,
            .second.id = tgt
        }
    });

    ecs_iter_t it = ecs_query_iter(world, q);
    char *json = ecs_iter_to_json(world, &it, NULL);
    test_assert(json != NULL);
    test_str(json, "{\"ids\":[[\"Rel\",\"Tgt\"]], \"results\":["
        "{\"ids\":[[\"Rel\",\"Tgt\"]], \"sources\":[0], \"entities\":[\"ent\"]}"
    "]}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_alert(void) {
    ecs_world_t *world = ecs_init();

    ECS_IMPORT(world, FlecsAlerts);

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_entity_t e2 = ecs_new_entity(world, "e2");

    ecs_alert(world, {
        .entity = ecs_new_entity(world, "position_without_velocity"),
        .filter.expr = "Position, !Velocity",
    });

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e2, Position, {30, 40});
    ecs_set(world, e1, Velocity, {1, 2});

    ecs_progress(world, 1.0); /* Evaluate alert logic */

    ecs_query_t *q = ecs_query_new(world, "Position");
    test_assert(q != NULL);

    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT;
    desc.serialize_ids = false;
    desc.serialize_values = false;
    desc.serialize_is_set = false;
    desc.serialize_sources = false;
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);
    test_str(json, "{\"ids\":[[\"Position\"]], \"results\":["
        "{\"entities\":[\"e1\"]}, "
        "{\"entities\":[\"e2\"], \"alerts\":true}]}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_no_this_alert_imported(void) {
    ecs_world_t *world = ecs_init();

    ECS_IMPORT(world, FlecsAlerts);

    ECS_COMPONENT(world, Position);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "Foo");
    ecs_set(world, e1, Position, {10, 20});

    ecs_rule_t *q = ecs_rule(world, {
        .expr = "Position($x)"
    });
    ecs_iter_t it = ecs_rule_iter(world, q);

    char *json = ecs_iter_to_json(world, &it, NULL);
    test_str(json, 
    "{"
        "\"ids\":[[\"Position\"]], "
        "\"vars\":[\"x\"], "
        "\"results\":[{"
            "\"ids\":[[\"Position\"]], "
            "\"sources\":[\"Foo\"], "
            "\"vars\":[\"Foo\"], "
            "\"values\":["
                "{\"x\":10, \"y\":20}"
            "]"
        "}]"
    "}");
    ecs_os_free(json);

    ecs_rule_fini(q);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_paged_iterator(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);

    ecs_set(world, 0, Position, {10, 20});
    ecs_entity_t e2 = ecs_set(world, 0, Position, {10, 20});
    ecs_entity_t e3 = ecs_set(world, 0, Position, {20, 30});
    ecs_entity_t e4 = ecs_set(world, 0, Position, {30, 40});
    ecs_set(world, 0, Position, {10, 20});

    ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){
        .terms = {{ .id = ecs_id(Position) }}
    });

    ecs_iter_t it = ecs_rule_iter(world, r);
    ecs_iter_t pit = ecs_page_iter(&it, 1, 3);

    test_bool(true, ecs_page_next(&pit));
    test_int(3, pit.count);
    test_uint(e2, pit.entities[0]);
    test_uint(e3, pit.entities[1]);
    test_uint(e4, pit.entities[2]);

    Position *p = ecs_field(&pit, Position, 1);
    test_int(p[0].x, 10);
    test_int(p[0].y, 20);
    test_int(p[1].x, 20);
    test_int(p[1].y, 30);
    test_int(p[2].x, 30);
    test_int(p[2].y, 40);

    test_bool(false, ecs_page_next(&pit));

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_paged_iterator_w_optional_component(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_set(world, 0, Position, {10, 20});
    ecs_entity_t e2 = ecs_set(world, 0, Position, {10, 20});
    ecs_entity_t e3 = ecs_set(world, 0, Position, {20, 30});
    ecs_entity_t e4 = ecs_set(world, 0, Position, {30, 40});
    ecs_entity_t e5 = ecs_set(world, 0, Position, {40, 50});

    ecs_set(world, e4, Velocity, {1, 2});
    ecs_set(world, e5, Velocity, {2, 3});

    ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){
        .terms = {
            { .id = ecs_id(Position) },
            { .id = ecs_id(Velocity), .oper = EcsOptional }
        }
    });

    ecs_iter_t it = ecs_rule_iter(world, r);
    ecs_iter_t pit = ecs_page_iter(&it, 1, 3);

    test_bool(true, ecs_page_next(&pit));
    test_int(2, pit.count);
    test_bool(true, ecs_field_is_set(&pit, 1));
    test_bool(false, ecs_field_is_set(&pit, 2));
    test_uint(e2, pit.entities[0]);
    test_uint(e3, pit.entities[1]);

    Position *p = ecs_field(&pit, Position, 1);
    test_int(p[0].x, 10);
    test_int(p[0].y, 20);
    test_int(p[1].x, 20);
    test_int(p[1].y, 30);

    test_bool(true, ecs_page_next(&pit));
    test_int(1, pit.count);
    test_bool(true, ecs_field_is_set(&pit, 1));
    test_bool(true, ecs_field_is_set(&pit, 2));

    test_uint(e4, pit.entities[0]);
    p = ecs_field(&pit, Position, 1);
    test_int(p[0].x, 30);
    test_int(p[0].y, 40);

    Velocity *v = ecs_field(&pit, Velocity, 2);
    test_int(v[0].x, 1);
    test_int(v[0].y, 2);

    test_bool(false, ecs_page_next(&pit));

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_paged_iterator_w_optional_tag(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_TAG(world, Tag);

    ecs_set(world, 0, Position, {10, 20});
    ecs_entity_t e2 = ecs_set(world, 0, Position, {10, 20});
    ecs_entity_t e3 = ecs_set(world, 0, Position, {20, 30});
    ecs_entity_t e4 = ecs_set(world, 0, Position, {30, 40});
    ecs_entity_t e5 = ecs_set(world, 0, Position, {40, 50});

    ecs_add(world, e4, Tag);
    ecs_add(world, e5, Tag);

    ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){
        .terms = {
            { .id = ecs_id(Position) },
            { .id = Tag, .oper = EcsOptional }
        }
    });

    ecs_iter_t it = ecs_rule_iter(world, r);
    ecs_iter_t pit = ecs_page_iter(&it, 1, 3);

    test_bool(true, ecs_page_next(&pit));
    test_int(2, pit.count);
    test_bool(true, ecs_field_is_set(&pit, 1));
    test_bool(false, ecs_field_is_set(&pit, 2));
    test_uint(e2, pit.entities[0]);
    test_uint(e3, pit.entities[1]);

    Position *p = ecs_field(&pit, Position, 1);
    test_int(p[0].x, 10);
    test_int(p[0].y, 20);
    test_int(p[1].x, 20);
    test_int(p[1].y, 30);

    test_bool(true, ecs_page_next(&pit));
    test_int(1, pit.count);
    test_bool(true, ecs_field_is_set(&pit, 1));
    test_bool(true, ecs_field_is_set(&pit, 2));

    test_uint(e4, pit.entities[0]);
    p = ecs_field(&pit, Position, 1);
    test_int(p[0].x, 30);
    test_int(p[0].y, 40);

    test_bool(false, ecs_page_next(&pit));

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_paged_iterator_w_vars(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Rel);
    ECS_TAG(world, ObjA);
    ECS_TAG(world, ObjB);

    ecs_new_w_pair(world, Rel, ObjA);
    ecs_entity_t e2 = ecs_new_w_pair(world, Rel, ObjA);
    ecs_entity_t e3 = ecs_new_w_pair(world, Rel, ObjA);
    ecs_entity_t e4 = ecs_new_w_pair(world, Rel, ObjB);
    ecs_new_w_pair(world, Rel, ObjB);

    ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){
        .expr = "(Rel, $Var)"
    });

    int32_t var = ecs_rule_find_var(r, "Var");

    ecs_iter_t it = ecs_rule_iter(world, r);
    ecs_iter_t pit = ecs_page_iter(&it, 1, 3);

    test_assert(var < pit.variable_count);
    test_str("Var", pit.variable_names[var]);

    test_bool(true, ecs_page_next(&pit));
    test_int(2, pit.count);
    test_uint(e2, pit.entities[0]);
    test_uint(e3, pit.entities[1]);
    test_uint(ObjA, ecs_iter_get_var(&pit, var));

    test_bool(true, ecs_page_next(&pit));
    test_int(1, pit.count);
    test_uint(e4, pit.entities[0]);
    test_uint(ObjB, ecs_iter_get_var(&pit, var));

    test_bool(false, ecs_page_next(&pit));

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_table(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_COMPONENT(world, Mass);
    ECS_TAG(world, Foo);
    ECS_TAG(world, Bar);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Velocity),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Mass),
        .members = {
            {"value", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_entity_t e2 = ecs_new_entity(world, "e2");
    ecs_entity_t e3 = ecs_new_entity(world, "e3");

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e2, Position, {20, 30});
    ecs_set(world, e3, Position, {30, 40});

    ecs_add(world, e1, Foo);
    ecs_add(world, e2, Foo);
    ecs_add(world, e2, Bar);

    ecs_set(world, e2, Velocity, {1, 1});
    ecs_set(world, e3, Mass, {100});

    ecs_filter_t *f = ecs_filter(world, {
        .terms = {
            { .id = ecs_id(Position) }
        }
    });

    ecs_iter_t it = ecs_filter_iter(world, f);

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_table = true;
    desc.serialize_ids = true;
    desc.serialize_entities = true;
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);

    test_str(json, "{\"results\":["
        "{"
            "\"ids\":[[\"Position\"], [\"Foo\"]], "
            "\"entities\":[\"e1\"], "
            "\"values\":[[{\"x\":10, \"y\":20}], 0]"
        "}, {"
            "\"ids\":[[\"Position\"], [\"Velocity\"], [\"Foo\"], [\"Bar\"]], "
            "\"entities\":[\"e2\"], "
            "\"values\":[[{\"x\":20, \"y\":30}], [{\"x\":1, \"y\":1}], 0, 0]"
        "}, {"
            "\"ids\":[[\"Position\"], [\"Mass\"]], "
            "\"entities\":[\"e3\"], "
            "\"values\":[[{\"x\":30, \"y\":40}], [{\"value\":100}]]}]"
        "}");
    
    ecs_os_free(json);

    ecs_filter_fini(f);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_table_w_id_labels(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_COMPONENT(world, Mass);
    ECS_TAG(world, Foo);
    ECS_TAG(world, Bar);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Velocity),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Mass),
        .members = {
            {"value", ecs_id(ecs_i32_t)}
        }
    });

    ecs_doc_set_name(world, ecs_id(Position), "position");
    ecs_doc_set_name(world, ecs_id(Velocity), "velocity");
    ecs_doc_set_name(world, ecs_id(Mass), "mass");

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_entity_t e2 = ecs_new_entity(world, "e2");
    ecs_entity_t e3 = ecs_new_entity(world, "e3");

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e2, Position, {20, 30});
    ecs_set(world, e3, Position, {30, 40});

    ecs_add(world, e1, Foo);
    ecs_add(world, e2, Foo);
    ecs_add(world, e2, Bar);

    ecs_set(world, e2, Velocity, {1, 1});
    ecs_set(world, e3, Mass, {100});

    ecs_filter_t *f = ecs_filter(world, {
        .terms = {
            { .id = ecs_id(Position) }
        }
    });

    ecs_iter_t it = ecs_filter_iter(world, f);

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_table = true;
    desc.serialize_entities = true;
    desc.serialize_id_labels = true;
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);

    test_str(json, "{\"results\":["
        "{"
            "\"id_labels\":[[\"position\"], [\"Foo\"]], "
            "\"entities\":[\"e1\"], "
            "\"values\":[[{\"x\":10, \"y\":20}], 0]"
        "}, {"
            "\"id_labels\":[[\"position\"], [\"velocity\"], [\"Foo\"], [\"Bar\"]], "
            "\"entities\":[\"e2\"], "
            "\"values\":[[{\"x\":20, \"y\":30}], [{\"x\":1, \"y\":1}], 0, 0]"
        "}, {"
            "\"id_labels\":[[\"position\"], [\"mass\"]], "
            "\"entities\":[\"e3\"], "
            "\"values\":[[{\"x\":30, \"y\":40}], [{\"value\":100}]]}]"
        "}");
    
    ecs_os_free(json);

    ecs_filter_fini(f);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_table_w_var_labels(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_COMPONENT(world, Mass);
    ECS_TAG(world, Foo);
    ECS_TAG(world, Bar);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Velocity),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Mass),
        .members = {
            {"value", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t parent = ecs_new_entity(world, "Parent");
    ecs_doc_set_name(world, parent, "parent");

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_entity_t e2 = ecs_new_entity(world, "e2");
    ecs_entity_t e3 = ecs_new_entity(world, "e3");

    ecs_add_pair(world, e1, EcsChildOf, parent);
    ecs_add_pair(world, e2, EcsChildOf, parent);
    ecs_add_pair(world, e3, EcsChildOf, parent);

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e2, Position, {20, 30});
    ecs_set(world, e3, Position, {30, 40});

    ecs_add(world, e1, Foo);
    ecs_add(world, e2, Foo);
    ecs_add(world, e2, Bar);

    ecs_set(world, e2, Velocity, {1, 1});
    ecs_set(world, e3, Mass, {100});

    ecs_rule_t *f = ecs_rule(world, {
        .terms = {
            { .id = ecs_id(Position) },
            { .first.id = EcsChildOf, .second.name = "p", .second.flags = EcsIsVariable }
        }
    });

    ecs_iter_t it = ecs_rule_iter(world, f);

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_table = true;
    desc.serialize_entities = true;
    desc.serialize_variable_labels = true;
    desc.serialize_ids = true;
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);

    test_str(json, "{\"vars\":[\"p\"], \"results\":["
        "{"
            "\"ids\":[[\"Position\"], [\"Foo\"]], "
            "\"var_labels\":[\"parent\"], "
            "\"parent\":\"Parent\", "
            "\"entities\":[\"e1\"], "
            "\"values\":[[{\"x\":10, \"y\":20}], 0]"
        "}, {"
            "\"ids\":[[\"Position\"], [\"Velocity\"], [\"Foo\"], [\"Bar\"]], "
            "\"var_labels\":[\"parent\"], "
            "\"parent\":\"Parent\", "
            "\"entities\":[\"e2\"], "
            "\"values\":[[{\"x\":20, \"y\":30}], [{\"x\":1, \"y\":1}], 0, 0]"
        "}, {"
            "\"ids\":[[\"Position\"], [\"Mass\"]], "
            "\"var_labels\":[\"parent\"], "
            "\"parent\":\"Parent\", "
            "\"entities\":[\"e3\"], "
            "\"values\":[[{\"x\":30, \"y\":40}], [{\"value\":100}]]}]"
        "}");

    ecs_os_free(json);

    ecs_rule_fini(f);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_table_w_private(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);
    ECS_COMPONENT(world, Mass);
    ECS_TAG(world, Foo);
    ECS_TAG(world, Bar);

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Velocity),
        .members = {
            {"x", ecs_id(ecs_i32_t)},
            {"y", ecs_id(ecs_i32_t)}
        }
    });

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Mass),
        .members = {
            {"value", ecs_id(ecs_i32_t)}
        }
    });

    ecs_entity_t parent = ecs_new_entity(world, "Parent");
    ecs_doc_set_name(world, parent, "parent");

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_entity_t e2 = ecs_new_entity(world, "e2");
    ecs_entity_t e3 = ecs_new_entity(world, "e3");

    ecs_add_pair(world, e1, EcsChildOf, parent);
    ecs_add_pair(world, e2, EcsChildOf, parent);
    ecs_add_pair(world, e3, EcsChildOf, parent);

    ecs_set(world, e1, Position, {10, 20});
    ecs_set(world, e2, Position, {20, 30});
    ecs_set(world, e3, Position, {30, 40});

    ecs_add(world, e1, Foo);
    ecs_add(world, e2, Foo);
    ecs_add(world, e2, Bar);

    ecs_set(world, e2, Velocity, {1, 1});
    ecs_set(world, e3, Mass, {100});

    ecs_rule_t *f = ecs_rule(world, {
        .terms = {
            { .id = ecs_id(Position) },
            { .first.id = EcsChildOf, .second.name = "p", .second.flags = EcsIsVariable }
        }
    });

    ecs_iter_t it = ecs_rule_iter(world, f);

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_table = true;
    desc.serialize_entities = true;
    desc.serialize_variable_labels = true;
    desc.serialize_ids = true;
    desc.serialize_private = true;
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);

    test_str(json, "{\"vars\":[\"p\"], \"results\":["
        "{"
            "\"ids\":[[\"Position\"], [\"Foo\"], [\"flecs.core.Identifier\",\"flecs.core.Name\"], [\"flecs.core.ChildOf\",\"Parent\"]], "
            "\"var_labels\":[\"parent\"], "
            "\"parent\":\"Parent\", "
            "\"entities\":[\"e1\"], "
            "\"values\":[[{\"x\":10, \"y\":20}], 0, 0, 0]"
        "}, {"
            "\"ids\":[[\"Position\"], [\"Velocity\"], [\"Foo\"], [\"Bar\"], [\"flecs.core.Identifier\",\"flecs.core.Name\"], [\"flecs.core.ChildOf\",\"Parent\"]], "
            "\"var_labels\":[\"parent\"], "
            "\"parent\":\"Parent\", "
            "\"entities\":[\"e2\"], "
            "\"values\":[[{\"x\":20, \"y\":30}], [{\"x\":1, \"y\":1}], 0, 0, 0, 0]"
        "}, {"
            "\"ids\":[[\"Position\"], [\"Mass\"], [\"flecs.core.Identifier\",\"flecs.core.Name\"], [\"flecs.core.ChildOf\",\"Parent\"]], "
            "\"var_labels\":[\"parent\"], "
            "\"parent\":\"Parent\", "
            "\"entities\":[\"e3\"], "
            "\"values\":[[{\"x\":30, \"y\":40}], [{\"value\":100}], 0, 0]}]"
        "}");

    ecs_os_free(json);

    ecs_rule_fini(f);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_world(void) {
    ecs_world_t *world = ecs_init();

    /* We can't test if the output for an exact value as this would change any
     * time something is added/removed to the world. */

    char *json_1 = ecs_world_to_json(world, NULL);
    test_assert(json_1 != NULL);

    char *json_2 = ecs_world_to_json(world, NULL);
    test_assert(json_2 != NULL);

    test_assert(json_1 != json_2);
    test_str(json_1, json_2);

    ecs_os_free(json_1);
    ecs_os_free(json_2);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_w_offset(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Tag);

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_add(world, e1, Tag);

    ecs_entity_t e2 = ecs_new_entity(world, "e2");
    ecs_add(world, e2, Tag);

    ecs_rule_t *r = ecs_rule_new(world, "Tag($this), ?$this(_)");
    ecs_iter_t it = ecs_rule_iter(world, r);

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_entities = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
        "{\"results\":[{\"entities\":[\"e1\"]}, {\"entities\":[\"e2\"]}]}");

    ecs_os_free(json);

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_labels_w_offset(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Tag);

    ecs_entity_t e1 = ecs_new_id(world);
    ecs_add(world, e1, Tag);
    ecs_doc_set_name(world, e1, "e1");

    ecs_entity_t e2 = ecs_new_id(world);
    ecs_add(world, e2, Tag);
    ecs_doc_set_name(world, e2, "e2");

    ecs_rule_t *r = ecs_rule_new(world, "Tag($this), ?$this(_)");
    ecs_iter_t it = ecs_rule_iter(world, r);

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_entity_labels = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
        "{\"results\":[{\"entity_labels\":[\"e1\"]}, {\"entity_labels\":[\"e2\"]}]}");

    ecs_os_free(json);

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_colors_w_offset(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Tag);

    ecs_entity_t e1 = ecs_new_id(world);
    ecs_add(world, e1, Tag);
    ecs_doc_set_color(world, e1, "e1");

    ecs_entity_t e2 = ecs_new_id(world);
    ecs_add(world, e2, Tag);
    ecs_doc_set_color(world, e2, "e2");

    ecs_rule_t *r = ecs_rule_new(world, "Tag($this), ?$this(_)");
    ecs_iter_t it = ecs_rule_iter(world, r);

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_colors = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    test_str(json, 
        "{\"results\":[{\"colors\":[\"e1\"]}, {\"colors\":[\"e2\"]}]}");

    ecs_os_free(json);

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_anonymous_entities_w_offset(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Tag);

    ecs_entity_t e1 = ecs_new_id(world);
    ecs_add(world, e1, Tag);

    ecs_entity_t e2 = ecs_new_id(world);
    ecs_add(world, e2, Tag);

    ecs_rule_t *r = ecs_rule_new(world, "Tag($this), ?$this(_)");
    ecs_iter_t it = ecs_rule_iter(world, r);

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_entities = true;
    char *json = ecs_iter_to_json(world, &it, &desc);

    char *expect = ecs_asprintf(
        "{\"results\":[{\"entities\":[%u]}, {\"entities\":[%u]}]}",
            (uint32_t)e1, (uint32_t)e2);

    test_str(json, expect);

    ecs_os_free(json);
    ecs_os_free(expect);

    ecs_rule_fini(r);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_term_labels(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);

    ecs_doc_set_name(world, ecs_id(Position), "position");

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_f32_t)},
            {"y", ecs_id(ecs_f32_t)}
        }
    });

    ecs_query_t *q = ecs_query_new(world, "Position");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_term_labels = true;
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);
    test_str(json, "{\"id_labels\":[[\"position\"]], \"results\":[]}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_id_labels(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);

    ecs_doc_set_name(world, ecs_id(Position), "position");

    ecs_struct_init(world, &(ecs_struct_desc_t){
        .entity = ecs_id(Position),
        .members = {
            {"x", ecs_id(ecs_f32_t)},
            {"y", ecs_id(ecs_f32_t)}
        }
    });

    ecs_entity_t e = ecs_new_entity(world, "ent");
    ecs_set(world, e, Position, {1, 2});

    ecs_query_t *q = ecs_query_new(world, "Position");
    ecs_iter_t it = ecs_query_iter(world, q);

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_id_labels = true;
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);
    test_str(json, "{\"results\":[{\"id_labels\":[[\"position\"]]}]}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_vars_for_query(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);

    ecs_query_t *q = ecs_query(world, {
        .filter.terms[0] = {
            .id = ecs_id(Position)
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_set(world, e1, Position, {1, 2});
    ecs_entity_t e2 = ecs_new_entity(world, "e2");
    ecs_set(world, e2, Position, {3, 4});

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_entities = true;
    desc.serialize_variables = true;
    ecs_iter_t it = ecs_query_iter(world, q);
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);
    test_str(json, "{\"results\":[{\"entities\":[\"e1\", \"e2\"]}]}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_var_labels_for_query(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);

    ecs_query_t *q = ecs_query(world, {
        .filter.terms[0] = {
            .id = ecs_id(Position)
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_set(world, e1, Position, {1, 2});
    ecs_entity_t e2 = ecs_new_entity(world, "e2");
    ecs_set(world, e2, Position, {3, 4});

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_entities = true;
    desc.serialize_variable_labels = true;
    ecs_iter_t it = ecs_query_iter(world, q);
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);
    test_str(json, "{\"results\":[{\"entities\":[\"e1\", \"e2\"]}]}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_var_ids_for_query(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);

    ecs_query_t *q = ecs_query(world, {
        .filter.terms[0] = {
            .id = ecs_id(Position)
        }
    });

    ecs_entity_t e1 = ecs_new_entity(world, "e1");
    ecs_set(world, e1, Position, {1, 2});
    ecs_entity_t e2 = ecs_new_entity(world, "e2");
    ecs_set(world, e2, Position, {3, 4});

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_entities = true;
    desc.serialize_variable_ids = true;
    ecs_iter_t it = ecs_query_iter(world, q);
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);
    test_str(json, "{\"results\":[{\"entities\":[\"e1\", \"e2\"]}]}");

    ecs_os_free(json);

    ecs_fini(world);
}

void SerializeIterToJson_serialize_null_doc_name(void) {
    ecs_world_t *world = ecs_init();

    ECS_TAG(world, Tag);

    ecs_entity_t e = ecs_new_entity(world, "foo");
    ecs_doc_set_name(world, e, "bar");
    ecs_doc_set_name(world, e, NULL);
    ecs_add(world, e, Tag);

    ecs_query_t *q = ecs_query(world, {
        .filter.terms[0] = {
            .id = Tag
        }
    });
    test_assert(q != NULL);

    ecs_iter_to_json_desc_t desc = {0};
    desc.serialize_entities = true;
    desc.serialize_entity_labels = true;

    ecs_iter_t it = ecs_query_iter(world, q);
    char *json = ecs_iter_to_json(world, &it, &desc);
    test_assert(json != NULL);
    test_str(json, "{\"results\":[{\"entities\":[\"foo\"]}]}");
    ecs_os_free(json);

    ecs_fini(world);
}
