Implement JSON_SORT_KEYS encoding flag
authorPetri Lehtinen <petri@digip.org>
Mon, 7 Dec 2009 11:16:45 +0000 (13:16 +0200)
committerPetri Lehtinen <petri@digip.org>
Wed, 16 Dec 2009 21:12:39 +0000 (23:12 +0200)
With this flag, the objects are sorted by key when encoding.

doc/apiref.rst
src/dump.c
src/jansson.h
test/bin/json_process.c
test/suites/encoding-flags/run [new file with mode: 0755]
test/suites/encoding-flags/sort-keys/env [new file with mode: 0644]
test/suites/encoding-flags/sort-keys/input [new file with mode: 0644]
test/suites/encoding-flags/sort-keys/output [new file with mode: 0644]

index dcd401c..fd2a7c9 100644 (file)
@@ -526,6 +526,13 @@ can be ORed together to obtain *flags*.
 
    .. versionadded:: 1.2
 
+``JSON_SORT_KEYS``
+   If this flag is used, all the objects in output are sorted by key.
+   This is useful e.g. if two JSON texts are diffed or visually
+   compared.
+
+   .. versionadded:: 1.2
+
 The following functions perform the actual JSON encoding. The result
 is in UTF-8.
 
index dc3fcbc..328e93b 100644 (file)
@@ -10,6 +10,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdint.h>
+#include <assert.h>
 
 #include <jansson.h>
 #include "jansson_private.h"
@@ -153,6 +154,11 @@ static int dump_string(const char *str, int ascii, dump_func dump, void *data)
     return dump("\"", 1, data);
 }
 
+static int object_key_cmp(const void *key1, const void *key2)
+{
+    return strcmp(*(const char **)key1, *(const char **)key2);
+}
+
 static int do_dump(const json_t *json, unsigned long flags, int depth,
                    dump_func dump, void *data)
 {
@@ -269,29 +275,96 @@ static int do_dump(const json_t *json, unsigned long flags, int depth,
             if(dump_indent(flags, depth + 1, 0, dump, data))
                 return -1;
 
-            while(iter)
+            if(flags & JSON_SORT_KEYS)
             {
-                void *next = json_object_iter_next((json_t *)json, iter);
+                /* Sort keys */
 
-                dump_string(json_object_iter_key(iter), ascii, dump, data);
-                if(dump(separator, separator_length, data) ||
-                   do_dump(json_object_iter_value(iter), flags, depth + 1,
-                           dump, data))
+                const char **keys;
+                unsigned int size;
+                unsigned int i;
+
+                size = json_object_size(json);
+                keys = malloc(size * sizeof(const char *));
+                if(!keys)
                     return -1;
 
-                if(next)
+                i = 0;
+                while(iter)
                 {
-                    if(dump(",", 1, data) ||
-                       dump_indent(flags, depth + 1, 1, dump, data))
-                        return -1;
+                    keys[i] = json_object_iter_key(iter);
+                    iter = json_object_iter_next((json_t *)json, iter);
+                    i++;
                 }
-                else
+                assert(i == size);
+
+                qsort(keys, size, sizeof(const char *), object_key_cmp);
+
+                for(i = 0; i < size; i++)
                 {
-                    if(dump_indent(flags, depth, 0, dump, data))
+                    const char *key;
+                    json_t *value;
+
+                    key = keys[i];
+                    value = json_object_get(json, key);
+                    assert(value);
+
+                    dump_string(key, ascii, dump, data);
+                    if(dump(separator, separator_length, data) ||
+                       do_dump(value, flags, depth + 1, dump, data))
+                    {
+                        free(keys);
                         return -1;
+                    }
+
+                    if(i < size - 1)
+                    {
+                        if(dump(",", 1, data) ||
+                           dump_indent(flags, depth + 1, 1, dump, data))
+                        {
+                            free(keys);
+                            return -1;
+                        }
+                    }
+                    else
+                    {
+                        if(dump_indent(flags, depth, 0, dump, data))
+                        {
+                            free(keys);
+                            return -1;
+                        }
+                    }
                 }
 
-                iter = next;
+                free(keys);
+            }
+            else
+            {
+                /* Don't sort keys */
+
+                while(iter)
+                {
+                    void *next = json_object_iter_next((json_t *)json, iter);
+
+                    dump_string(json_object_iter_key(iter), ascii, dump, data);
+                    if(dump(separator, separator_length, data) ||
+                       do_dump(json_object_iter_value(iter), flags, depth + 1,
+                               dump, data))
+                        return -1;
+
+                    if(next)
+                    {
+                        if(dump(",", 1, data) ||
+                           dump_indent(flags, depth + 1, 1, dump, data))
+                            return -1;
+                    }
+                    else
+                    {
+                        if(dump_indent(flags, depth, 0, dump, data))
+                            return -1;
+                    }
+
+                    iter = next;
+                }
             }
 
             object->visited = 0;
index d59fe10..607dfb5 100644 (file)
@@ -144,6 +144,7 @@ json_t *json_load_file(const char *path, json_error_t *error);
 #define JSON_INDENT(n)      (n & 0xFF)
 #define JSON_COMPACT        0x100
 #define JSON_ENSURE_ASCII   0x200
+#define JSON_SORT_KEYS      0x400
 
 char *json_dumps(const json_t *json, unsigned long flags);
 int json_dumpf(const json_t *json, FILE *output, unsigned long flags);
index e7be614..794e307 100644 (file)
@@ -53,6 +53,9 @@ int main(int argc, char *argv[])
     if(getenv_int("JSON_ENSURE_ASCII"))
         flags |= JSON_ENSURE_ASCII;
 
+    if(getenv_int("JSON_SORT_KEYS"))
+        flags |= JSON_SORT_KEYS;
+
     json = json_loadf(stdin, &error);
     if(!json) {
         fprintf(stderr, "%d\n%s\n", error.line, error.text);
diff --git a/test/suites/encoding-flags/run b/test/suites/encoding-flags/run
new file mode 100755 (executable)
index 0000000..a65fe5b
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Petri Lehtinen <petri@digip.org>
+#
+# Jansson is free software; you can redistribute it and/or modify
+# it under the terms of the MIT license. See LICENSE for details.
+
+is_test() {
+    test -d $test_path
+}
+
+run_test() {
+    (
+        if [ -f $test_path/env ]; then
+            . $test_path/env
+        fi
+        $json_process <$test_path/input >$test_log/stdout 2>$test_log/stderr
+    )
+    valgrind_check $test_log/stderr || return 1
+    cmp -s $test_path/output $test_log/stdout
+}
+
+show_error() {
+    valgrind_show_error && return
+
+    echo "EXPECTED OUTPUT:"
+    nl -bn $test_path/output
+    echo "ACTUAL OUTPUT:"
+    nl -bn $test_log/stdout
+}
+
+. $top_srcdir/test/scripts/run-tests.sh
diff --git a/test/suites/encoding-flags/sort-keys/env b/test/suites/encoding-flags/sort-keys/env
new file mode 100644 (file)
index 0000000..00529e7
--- /dev/null
@@ -0,0 +1 @@
+export JSON_SORT_KEYS=1
diff --git a/test/suites/encoding-flags/sort-keys/input b/test/suites/encoding-flags/sort-keys/input
new file mode 100644 (file)
index 0000000..66951d6
--- /dev/null
@@ -0,0 +1 @@
+{"foo": 1, "bar": 2, "baz": 3, "quux": 4}
diff --git a/test/suites/encoding-flags/sort-keys/output b/test/suites/encoding-flags/sort-keys/output
new file mode 100644 (file)
index 0000000..132d9df
--- /dev/null
@@ -0,0 +1 @@
+{"bar": 2, "baz": 3, "foo": 1, "quux": 4}
\ No newline at end of file