Merge pull request #293 from akheron/object-insertion-order

Always preserve insertion order of object items
This commit is contained in:
Petri Lehtinen 2016-08-25 20:20:34 +03:00 committed by GitHub
commit 14573dc920
9 changed files with 68 additions and 111 deletions

View File

@ -659,7 +659,8 @@ allowed in object keys.
/* block of code that uses key and value */
}
The items are not returned in any particular order.
The items are returned in the order they were inserted to the
object.
**Note:** It's not safe to call ``json_object_del(object, key)``
during iteration. If you need to, use
@ -685,9 +686,8 @@ allowed in object keys.
The following functions can be used to iterate through all key-value
pairs in an object. The items are not returned in any particular order,
as this would require sorting due to the internal hashtable
implementation.
pairs in an object. The items are returned in the order they were
inserted to the object.
.. function:: void *json_object_iter(json_t *object)
@ -885,10 +885,13 @@ can be ORed together to obtain *flags*.
compared.
``JSON_PRESERVE_ORDER``
If this flag is used, object keys in the output are sorted into the
same order in which they were first inserted to the object. For
example, decoding a JSON text and then encoding with this flag
preserves the order of object keys.
**Deprecated since version 2.8:** Order of object keys
is always preserved.
Prior to version 2.8: If this flag is used, object keys in the
output are sorted into the same order in which they were first
inserted to the object. For example, decoding a JSON text and then
encoding with this flag preserves the order of object keys.
``JSON_ENCODE_ANY``
Specifying this flag makes it possible to encode any JSON value on
@ -1508,9 +1511,7 @@ the same child values in the copied value. Deep copying makes a fresh
copy of the child values, too. Moreover, all the child values are deep
copied in a recursive fashion.
Copying objects doesn't preserve the insertion order of keys. Deep
copying also loses the key insertion order of any objects deeper in
the hierarchy.
Copying objects preserves the insertion order of keys.
.. function:: json_t *json_copy(json_t *value)

View File

@ -25,11 +25,6 @@
#define FLAGS_TO_INDENT(f) ((f) & 0x1F)
#define FLAGS_TO_PRECISION(f) (((f) >> 11) & 0x1F)
struct object_key {
size_t serial;
const char *key;
};
static int dump_to_strbuffer(const char *buffer, size_t size, void *data)
{
return strbuffer_append_bytes((strbuffer_t *)data, buffer, size);
@ -165,18 +160,9 @@ static int dump_string(const char *str, size_t len, json_dump_callback_t dump, v
return dump("\"", 1, data);
}
static int object_key_compare_keys(const void *key1, const void *key2)
static int compare_keys(const void *key1, const void *key2)
{
return strcmp(((const struct object_key *)key1)->key,
((const struct object_key *)key2)->key);
}
static int object_key_compare_serials(const void *key1, const void *key2)
{
size_t a = ((const struct object_key *)key1)->serial;
size_t b = ((const struct object_key *)key2)->serial;
return a < b ? -1 : a == b ? 0 : 1;
return strcmp(*(const char **)key1, *(const char **)key2);
}
static int do_dump(const json_t *json, size_t flags, int depth,
@ -309,40 +295,33 @@ static int do_dump(const json_t *json, size_t flags, int depth,
if(dump_indent(flags, depth + 1, 0, dump, data))
goto object_error;
if(flags & JSON_SORT_KEYS || flags & JSON_PRESERVE_ORDER)
if(flags & JSON_SORT_KEYS)
{
struct object_key *keys;
const char **keys;
size_t size, i;
int (*cmp_func)(const void *, const void *);
size = json_object_size(json);
keys = jsonp_malloc(size * sizeof(struct object_key));
keys = jsonp_malloc(size * sizeof(const char *));
if(!keys)
goto object_error;
i = 0;
while(iter)
{
keys[i].serial = hashtable_iter_serial(iter);
keys[i].key = json_object_iter_key(iter);
keys[i] = json_object_iter_key(iter);
iter = json_object_iter_next((json_t *)json, iter);
i++;
}
assert(i == size);
if(flags & JSON_SORT_KEYS)
cmp_func = object_key_compare_keys;
else
cmp_func = object_key_compare_serials;
qsort(keys, size, sizeof(struct object_key), cmp_func);
qsort(keys, size, sizeof(const char *), compare_keys);
for(i = 0; i < size; i++)
{
const char *key;
json_t *value;
key = keys[i].key;
key = keys[i];
value = json_object_get(json, key);
assert(value);

View File

@ -34,6 +34,7 @@ extern volatile uint32_t hashtable_seed;
#include "lookup3.h"
#define list_to_pair(list_) container_of(list_, pair_t, list)
#define ordered_list_to_pair(list_) container_of(list_, pair_t, ordered_list)
#define hash_str(key) ((size_t)hashlittle((key), strlen(key), hashtable_seed))
static JSON_INLINE void list_init(list_t *list)
@ -126,6 +127,7 @@ static int hashtable_do_del(hashtable_t *hashtable,
bucket->last = pair->list.prev;
list_remove(&pair->list);
list_remove(&pair->ordered_list);
json_decref(pair->value);
jsonp_free(pair);
@ -197,6 +199,7 @@ int hashtable_init(hashtable_t *hashtable)
return -1;
list_init(&hashtable->list);
list_init(&hashtable->ordered_list);
for(i = 0; i < hashsize(hashtable->order); i++)
{
@ -213,9 +216,7 @@ void hashtable_close(hashtable_t *hashtable)
jsonp_free(hashtable->buckets);
}
int hashtable_set(hashtable_t *hashtable,
const char *key, size_t serial,
json_t *value)
int hashtable_set(hashtable_t *hashtable, const char *key, json_t *value)
{
pair_t *pair;
bucket_t *bucket;
@ -253,12 +254,13 @@ int hashtable_set(hashtable_t *hashtable,
return -1;
pair->hash = hash;
pair->serial = serial;
strncpy(pair->key, key, len + 1);
pair->value = value;
list_init(&pair->list);
list_init(&pair->ordered_list);
insert_to_bucket(hashtable, bucket, &pair->list);
list_insert(&hashtable->ordered_list, &pair->ordered_list);
hashtable->size++;
}
@ -300,12 +302,13 @@ void hashtable_clear(hashtable_t *hashtable)
}
list_init(&hashtable->list);
list_init(&hashtable->ordered_list);
hashtable->size = 0;
}
void *hashtable_iter(hashtable_t *hashtable)
{
return hashtable_iter_next(hashtable, &hashtable->list);
return hashtable_iter_next(hashtable, &hashtable->ordered_list);
}
void *hashtable_iter_at(hashtable_t *hashtable, const char *key)
@ -321,38 +324,32 @@ void *hashtable_iter_at(hashtable_t *hashtable, const char *key)
if(!pair)
return NULL;
return &pair->list;
return &pair->ordered_list;
}
void *hashtable_iter_next(hashtable_t *hashtable, void *iter)
{
list_t *list = (list_t *)iter;
if(list->next == &hashtable->list)
if(list->next == &hashtable->ordered_list)
return NULL;
return list->next;
}
void *hashtable_iter_key(void *iter)
{
pair_t *pair = list_to_pair((list_t *)iter);
pair_t *pair = ordered_list_to_pair((list_t *)iter);
return pair->key;
}
size_t hashtable_iter_serial(void *iter)
{
pair_t *pair = list_to_pair((list_t *)iter);
return pair->serial;
}
void *hashtable_iter_value(void *iter)
{
pair_t *pair = list_to_pair((list_t *)iter);
pair_t *pair = ordered_list_to_pair((list_t *)iter);
return pair->value;
}
void hashtable_iter_set(void *iter, json_t *value)
{
pair_t *pair = list_to_pair((list_t *)iter);
pair_t *pair = ordered_list_to_pair((list_t *)iter);
json_decref(pair->value);
pair->value = value;

View File

@ -21,9 +21,9 @@ struct hashtable_list {
too */
struct hashtable_pair {
struct hashtable_list list;
struct hashtable_list ordered_list;
size_t hash;
json_t *value;
size_t serial;
char key[1];
};
@ -37,11 +37,12 @@ typedef struct hashtable {
struct hashtable_bucket *buckets;
size_t order; /* hashtable has pow(2, order) buckets */
struct hashtable_list list;
struct hashtable_list ordered_list;
} hashtable_t;
#define hashtable_key_to_iter(key_) \
(&(container_of(key_, struct hashtable_pair, key)->list))
(&(container_of(key_, struct hashtable_pair, key)->ordered_list))
/**
@ -80,9 +81,7 @@ void hashtable_close(hashtable_t *hashtable);
*
* Returns 0 on success, -1 on failure (out of memory).
*/
int hashtable_set(hashtable_t *hashtable,
const char *key, size_t serial,
json_t *value);
int hashtable_set(hashtable_t *hashtable, const char *key, json_t *value);
/**
* hashtable_get - Get a value associated with a key
@ -159,13 +158,6 @@ void *hashtable_iter_next(hashtable_t *hashtable, void *iter);
*/
void *hashtable_iter_key(void *iter);
/**
* hashtable_iter_serial - Retrieve the serial number pointed to by an iterator
*
* @iter: The iterator
*/
size_t hashtable_iter_serial(void *iter);
/**
* hashtable_iter_value - Retrieve the value pointed by an iterator
*

View File

@ -34,7 +34,6 @@
typedef struct {
json_t json;
hashtable_t hashtable;
size_t serial;
int visited;
} json_object_t;

View File

@ -464,7 +464,7 @@ static int unpack_object(scanner_t *s, json_t *root, va_list *ap)
if(unpack(s, value, ap))
goto out;
hashtable_set(&key_set, key, 0, json_null());
hashtable_set(&key_set, key, json_null());
next_token(s);
}

View File

@ -67,7 +67,6 @@ json_t *json_object(void)
return NULL;
}
object->serial = 0;
object->visited = 0;
return &object->json;
@ -115,7 +114,7 @@ int json_object_set_new_nocheck(json_t *json, const char *key, json_t *value)
}
object = json_to_object(json);
if(hashtable_set(&object->hashtable, key, object->serial++, value))
if(hashtable_set(&object->hashtable, key, value))
{
json_decref(value);
return -1;
@ -154,9 +153,7 @@ int json_object_clear(json_t *json)
return -1;
object = json_to_object(json);
hashtable_clear(&object->hashtable);
object->serial = 0;
return 0;
}

View File

@ -232,6 +232,9 @@ static void test_copy_object(void)
const char *json_object_text =
"{\"foo\": \"bar\", \"a\": 1, \"b\": 3.141592, \"c\": [1,2,3,4]}";
const char *keys[] = {"foo", "a", "b", "c"};
int i;
json_t *object, *copy;
void *iter;
@ -247,6 +250,7 @@ static void test_copy_object(void)
if(!json_equal(copy, object))
fail("copying an object produces an inequal copy");
i = 0;
iter = json_object_iter(object);
while(iter)
{
@ -258,9 +262,13 @@ static void test_copy_object(void)
value2 = json_object_get(copy, key);
if(value1 != value2)
fail("deep copying an object modifies its items");
fail("copying an object modifies its items");
if (strcmp(key, keys[i]) != 0)
fail("copying an object doesn't preserve key order");
iter = json_object_iter_next(object, iter);
i++;
}
json_decref(object);
@ -272,6 +280,9 @@ static void test_deep_copy_object(void)
const char *json_object_text =
"{\"foo\": \"bar\", \"a\": 1, \"b\": 3.141592, \"c\": [1,2,3,4]}";
const char *keys[] = {"foo", "a", "b", "c"};
int i;
json_t *object, *copy;
void *iter;
@ -287,6 +298,7 @@ static void test_deep_copy_object(void)
if(!json_equal(copy, object))
fail("deep copying an object produces an inequal copy");
i = 0;
iter = json_object_iter(object);
while(iter)
{
@ -300,7 +312,11 @@ static void test_deep_copy_object(void)
if(value1 == value2)
fail("deep copying an object doesn't copy its items");
if (strcmp(key, keys[i]) != 0)
fail("deep copying an object doesn't preserve key order");
iter = json_object_iter_next(object, iter);
i++;
}
json_decref(object);

View File

@ -275,11 +275,7 @@ static void test_set_nocheck()
static void test_iterators()
{
int i;
json_t *object, *foo, *bar, *baz;
const char *iter_keys[3];
int have_key[3] = { 0, 0, 0 };
json_t *iter_values[3];
void *iter;
if(json_object_iter(NULL))
@ -306,50 +302,30 @@ static void test_iterators()
iter = json_object_iter(object);
if(!iter)
fail("unable to get iterator");
iter_keys[0] = json_object_iter_key(iter);
iter_values[0] = json_object_iter_value(iter);
if (strcmp(json_object_iter_key(iter), "a") != 0)
fail("iterating doesn't yield keys in order");
if (json_object_iter_value(iter) != foo)
fail("iterating doesn't yield values in order");
iter = json_object_iter_next(object, iter);
if(!iter)
fail("unable to increment iterator");
iter_keys[1] = json_object_iter_key(iter);
iter_values[1] = json_object_iter_value(iter);
if (strcmp(json_object_iter_key(iter), "b") != 0)
fail("iterating doesn't yield keys in order");
if (json_object_iter_value(iter) != bar)
fail("iterating doesn't yield values in order");
iter = json_object_iter_next(object, iter);
if(!iter)
fail("unable to increment iterator");
iter_keys[2] = json_object_iter_key(iter);
iter_values[2] = json_object_iter_value(iter);
if (strcmp(json_object_iter_key(iter), "c") != 0)
fail("iterating doesn't yield keys in order");
if (json_object_iter_value(iter) != baz)
fail("iterating doesn't yield values in order");
if(json_object_iter_next(object, iter) != NULL)
fail("able to iterate over the end");
/* Check that keys have correct values */
for (i = 0; i < 3; i++) {
if (strcmp(iter_keys[i], "a") == 0) {
if (iter_values[i] != foo)
fail("wrong value for iter key a");
else
have_key[0] = 1;
} else if (strcmp(iter_keys[i], "b") == 0) {
if (iter_values[i] != bar)
fail("wrong value for iter key b");
else
have_key[1] = 1;
} else if (strcmp(iter_keys[i], "c") == 0) {
if (iter_values[i] != baz)
fail("wrong value for iter key c");
else
have_key[2] = 1;
}
}
/* Check that we got all keys */
for(i = 0; i < 3; i++) {
if(!have_key[i])
fail("a key wasn't iterated over");
}
if(json_object_iter_at(object, "foo"))
fail("json_object_iter_at() succeeds for non-existent key");