GameMaker: Minifying JSON

If you've spent some time working with JSON in GameMaker: Studio, you may have noticed that the built-in functions aren't exactly interested at making output laconic - they always write numbers with 6-digit precision, meaning that 3.5 always becomes 3.500000 even though the extra zeroes serve no purpose whatsoever.

The problem becomes more prominent when encoding data that is largely numbers - say, if writing down a shuffled sequence of numbers,

var map = ds_map_create();
//
var list = ds_list_create();
for (var i = 0; i < 32; i++) ds_list_add(list, i);
ds_list_shuffle(list);
ds_map_add_list(map, "order", list);
//
var json = json_encode(map);
ds_map_destroy(map); // destroys the list in the map as well
show_debug_message(json);

The output is as following:

{ "order": [ 30.000000, 22.000000, 2.000000, 8.000000, 17.000000, 14.000000, 25.000000, 9.000000, 20.000000, 29.000000, 10.000000, 26.000000, 6.000000, 15.000000, 21.000000, 1.000000, 11.000000, 3.000000, 24.000000, 12.000000, 19.000000, 31.000000, 7.000000, 28.000000, 18.000000, 4.000000, 0.000000, 5.000000, 27.000000, 16.000000, 23.000000, 13.000000 ] }

Where, out of 357 bytes, 250 bytes are whitespace or unneeded "precision".
A little inconvenient, if you don't have a compression algorithm on hand.
But, of course, that can be helped with a script.

The script in question is as following:

/// json_minify(json_string)
var s = argument0;
var i = 1, n = string_length(s);
var r = buffer_create(n + 1, buffer_grow, 1); // string buffer, for perforamnce.
while (i <= n) {
    var q = i;
    var c = string_ord_at(s, i);
    i += 1;
    switch (c) {
        case 9: case 10: case 13: case 32: // (insignificant whitespace)
            break;
        case ord('"'): // string
            while (i <= n) {
                c = string_ord_at(s, i);
                if (c != ord("\")) { // regular characters
                    i += 1;
                    if (c == ord('"')) break; // string ends
                } else i += 2; // skip over escape characters, e.g. `\"`
            }
            buffer_write(r, buffer_text, string_copy(s, q, i - q));
            break;
        default:
            if (c >= ord("0") && c <= ord("9")) { // numbers
                var pre = true; // whether reading pre-dot or not
                var till = q; // index at which meaningful part of the number ends
                while (i <= n) {
                    c = string_ord_at(s, i);
                    if (c == ord(".")) {
                        pre = false;
                        i += 1;
                    } else if (c >= ord("0") && c <= ord("9")) {
                        // write all pre-dot, and till the last non-zero after dot:
                        if (pre || c != ord("0")) till = i;
                        i += 1;
                    } else break;
                }
                buffer_write(r, buffer_text, string_copy(s, q, till - q + 1));
            } else buffer_write(r, buffer_text, string_char_at(s, q)); // other things
    } // switch
} // while
//
buffer_write(r, buffer_u8, 0); // string delimiter `\0`
buffer_seek(r, buffer_seek_start, 0); // rewind the buffer
s = buffer_read(r, buffer_string); // read the string written to it
buffer_delete(r); // remove the string buffer
return s;

So, in short, it iterates over characters in the string, while accumulating the matching ones into a buffer. Then it reads the resulting string from a buffer.

When ran on the earlier-shown snippet, it yields

{"order":[30,22,2,8,17,14,25,9,20,29,10,26,6,15,21,1,11,3,24,12,19,31,7,28,18,4,0,5,27,16,23,13]}

That's 97 bytes, which is about 1/3 of the original size.

In "real-world" uses there are usually slightly more strings and property names, so minifying may not compact the data this well, but 30..45% size reduction is still common, and nice to have.

Have fun!

Related posts:

2 thoughts on “GameMaker: Minifying JSON

Leave a Reply

Your email address will not be published. Required fields are marked *