Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iterating over map or array returned with MMDB_get_value() #255

Open
gonzus opened this issue Apr 7, 2021 · 6 comments
Open

Iterating over map or array returned with MMDB_get_value() #255

gonzus opened this issue Apr 7, 2021 · 6 comments

Comments

@gonzus
Copy link

gonzus commented Apr 7, 2021

I am working with the example data from the library's documentation:

{
    "names": {
        "en": "Germany",
        "de": "Deutschland"
    },
    "cities": [ "Berlin", "Frankfurt" ]
}

Say I call

MMDB_get_value(&result.entry, &entry_data, "names", NULL);

This will produce entry_data.type == 7, which is a MAP, as expected. How can I then retrieve the values for specific keys in this map? Because entry_data is a MMDB_entry_data_s and NOT a MMDB_entry_s, I cannot pass it into MMDB_get_value.

Same question would apply for an array:

MMDB_get_value(&result.entry, &entry_data, "cities", NULL);

This will produce with entry_data.type == 11, which is an ARRAY, as expected. How can I then retrieve the values for specific positions in this array?

@oschwald
Copy link
Member

oschwald commented Apr 7, 2021

To iterate over collection, you will need to use the offset_to_next field.

See this example for maps:

libmaxminddb/src/maxminddb.c

Lines 1255 to 1283 in ed7a425

while (size-- > 0) {
MMDB_entry_data_s key, value;
CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, &key);
uint32_t offset_to_value = key.offset_to_next;
if (MMDB_DATA_TYPE_UTF8_STRING != key.type) {
return MMDB_INVALID_DATA_ERROR;
}
if (key.data_size == path_elem_len &&
!memcmp(path_elem, key.utf8_string, path_elem_len)) {
DEBUG_MSG("found key matching path elem");
CHECKED_DECODE_ONE_FOLLOW(mmdb, offset_to_value, &value);
memcpy(entry_data, &value, sizeof(MMDB_entry_data_s));
return MMDB_SUCCESS;
} else {
/* We don't want to follow a pointer here. If the next element is
* a pointer we simply skip it and keep going */
CHECKED_DECODE_ONE(mmdb, offset_to_value, &value);
int status = skip_map_or_array(mmdb, &value);
if (MMDB_SUCCESS != status) {
return status;
}
offset = value.offset_to_next;
}
}

And for arrays:

libmaxminddb/src/maxminddb.c

Lines 1231 to 1239 in ed7a425

for (int i = 0; i < array_index; i++) {
/* We don't want to follow a pointer here. If the next element is a
* pointer we simply skip it and keep going */
CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data);
int status = skip_map_or_array(mmdb, entry_data);
if (MMDB_SUCCESS != status) {
return status;
}
}

@gonzus
Copy link
Author

gonzus commented Apr 8, 2021

Thanks for the quick reply @oschwald. Is there a way to directly retrieve, without iteration, the value for a given key in a map, or the value for a given position in an array?

Also, there seems to be no public API to do any of this; the code you pointed to depends on a couple of macros and static functions, which I would have to replicate in my code. Is this correct? If so, are there any plans to expose such APIs as part of the stable public interface of the library? I would love to have the equivalent of at least map_retrieve(key), map_get_keys(), array_get_size(), array_fetch(position).

@oschwald
Copy link
Member

oschwald commented Apr 8, 2021

You can look up particular map values or array index values directly with MMDB_get_values(), e.g., from your example above:

MMDB_get_value(&result.entry, &entry_data, "names", "en", NULL);

This would look up the value for the en key of the names map.

Similarly, the following would look up the first city:

MMDB_get_value(&result.entry, &entry_data, "cities", "0", NULL);

@gonzus
Copy link
Author

gonzus commented Apr 8, 2021

Yes, this is how I am doing it today. But if I know before hand I will access several keys inside a map, I think it would be more efficient to first get the map entry, and then get the value for each key, instead of repeatedly searching for "names" / "en", "names" / "es", "names" / "de", etc.

@oschwald
Copy link
Member

oschwald commented Apr 8, 2021

I see. I don't think we expose any functions to do this directly, although you likely could just create a new MMDB_entry_s with the offset to the map and then use MMDB_get_value on that.

@gonzus
Copy link
Author

gonzus commented Apr 8, 2021

Ok, thanks for all the hints. I still think it would be nice to have an official API for this kind of access, but I understand if this is out of the scope for the library -- feel free to close the issue if this is not in the plans.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

2 participants