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

View File

@ -25,11 +25,6 @@
#define FLAGS_TO_INDENT(f) ((f) & 0x1F) #define FLAGS_TO_INDENT(f) ((f) & 0x1F)
#define FLAGS_TO_PRECISION(f) (((f) >> 11) & 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) static int dump_to_strbuffer(const char *buffer, size_t size, void *data)
{ {
return strbuffer_append_bytes((strbuffer_t *)data, buffer, size); 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); 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, return strcmp(*(const char **)key1, *(const char **)key2);
((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;
} }
static int do_dump(const json_t *json, size_t flags, int depth, 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)) if(dump_indent(flags, depth + 1, 0, dump, data))
goto object_error; 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; size_t size, i;
int (*cmp_func)(const void *, const void *);
size = json_object_size(json); size = json_object_size(json);
keys = jsonp_malloc(size * sizeof(struct object_key)); keys = jsonp_malloc(size * sizeof(const char *));
if(!keys) if(!keys)
goto object_error; goto object_error;
i = 0; i = 0;
while(iter) while(iter)
{ {
keys[i].serial = hashtable_iter_serial(iter); keys[i] = json_object_iter_key(iter);
keys[i].key = json_object_iter_key(iter);
iter = json_object_iter_next((json_t *)json, iter); iter = json_object_iter_next((json_t *)json, iter);
i++; i++;
} }
assert(i == size); assert(i == size);
if(flags & JSON_SORT_KEYS) qsort(keys, size, sizeof(const char *), compare_keys);
cmp_func = object_key_compare_keys;
else
cmp_func = object_key_compare_serials;
qsort(keys, size, sizeof(struct object_key), cmp_func);
for(i = 0; i < size; i++) for(i = 0; i < size; i++)
{ {
const char *key; const char *key;
json_t *value; json_t *value;
key = keys[i].key; key = keys[i];
value = json_object_get(json, key); value = json_object_get(json, key);
assert(value); assert(value);

View File

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

View File

@ -21,9 +21,9 @@ struct hashtable_list {
too */ too */
struct hashtable_pair { struct hashtable_pair {
struct hashtable_list list; struct hashtable_list list;
struct hashtable_list ordered_list;
size_t hash; size_t hash;
json_t *value; json_t *value;
size_t serial;
char key[1]; char key[1];
}; };
@ -37,11 +37,12 @@ typedef struct hashtable {
struct hashtable_bucket *buckets; struct hashtable_bucket *buckets;
size_t order; /* hashtable has pow(2, order) buckets */ size_t order; /* hashtable has pow(2, order) buckets */
struct hashtable_list list; struct hashtable_list list;
struct hashtable_list ordered_list;
} hashtable_t; } hashtable_t;
#define hashtable_key_to_iter(key_) \ #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). * Returns 0 on success, -1 on failure (out of memory).
*/ */
int hashtable_set(hashtable_t *hashtable, int hashtable_set(hashtable_t *hashtable, const char *key, json_t *value);
const char *key, size_t serial,
json_t *value);
/** /**
* hashtable_get - Get a value associated with a key * 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); 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 * hashtable_iter_value - Retrieve the value pointed by an iterator
* *

View File

@ -34,7 +34,6 @@
typedef struct { typedef struct {
json_t json; json_t json;
hashtable_t hashtable; hashtable_t hashtable;
size_t serial;
int visited; int visited;
} json_object_t; } 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)) if(unpack(s, value, ap))
goto out; goto out;
hashtable_set(&key_set, key, 0, json_null()); hashtable_set(&key_set, key, json_null());
next_token(s); next_token(s);
} }

View File

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

View File

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

View File

@ -275,11 +275,7 @@ static void test_set_nocheck()
static void test_iterators() static void test_iterators()
{ {
int i;
json_t *object, *foo, *bar, *baz; 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; void *iter;
if(json_object_iter(NULL)) if(json_object_iter(NULL))
@ -306,50 +302,30 @@ static void test_iterators()
iter = json_object_iter(object); iter = json_object_iter(object);
if(!iter) if(!iter)
fail("unable to get iterator"); fail("unable to get iterator");
iter_keys[0] = json_object_iter_key(iter); if (strcmp(json_object_iter_key(iter), "a") != 0)
iter_values[0] = json_object_iter_value(iter); 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); iter = json_object_iter_next(object, iter);
if(!iter) if(!iter)
fail("unable to increment iterator"); fail("unable to increment iterator");
iter_keys[1] = json_object_iter_key(iter); if (strcmp(json_object_iter_key(iter), "b") != 0)
iter_values[1] = json_object_iter_value(iter); 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); iter = json_object_iter_next(object, iter);
if(!iter) if(!iter)
fail("unable to increment iterator"); fail("unable to increment iterator");
iter_keys[2] = json_object_iter_key(iter); if (strcmp(json_object_iter_key(iter), "c") != 0)
iter_values[2] = json_object_iter_value(iter); 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) if(json_object_iter_next(object, iter) != NULL)
fail("able to iterate over the end"); 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")) if(json_object_iter_at(object, "foo"))
fail("json_object_iter_at() succeeds for non-existent key"); fail("json_object_iter_at() succeeds for non-existent key");