GameMaker: Beautifying/pretty-printing JSON (2024)

Table of Contents
The idea The code Related posts:

Partially related to an earlier blog post about minifying JSON (which I had just updated), but adressing a different problem - sometimes, rather than trying to make your JSON more compact (and, consequently, less readable), you may want to make it more readable - be that for your own debugging purposes, or not to scare players away from files that they are allowed to edit.

Take Nuclear Throne, for example. The save file is a big nested JSON structure, and, needless to say, you aren't getting around passing it through an external JSON beautifier if you want to be able to make sense of it:

GameMaker: Beautifying/pretty-printing JSON (1)

With a bit of string processing, however, you can have it printed out nicely readable:

GameMaker: Beautifying/pretty-printing JSON (2)

So this post is about that.

The idea

The general outline of process is as following:

  • Trim any unnecessary whitespace (in case it's inconsistent).
  • Cut trailing zeroes from numeric values (4.500000 -> 4.5, 1.000000 -> 1).
  • Add a space after each colon if there isn't one ("key": "value", "key": [...], ...).
  • After each opening bracket not immediately followed by a closing bracket, increase indentation level and add a new line (if followed, use compact format - [], {}).
  • Add a new line (with indentation level in mind) after each comma.
  • Reduce indentation level and add a new line before each closing bracket (except for those preceded by an opening bracket).
  • Make sure not to reformat contents of strings.

The code

The code is largely based on my 2018 version of minifier script.

First, you'll need a helper script called buffer_write_slice:

/// buffer_write_slice(buffer, data_buffer, data_start, data_end)var start = argument2;var next = argument3 - start;if (next <= 0) exit;var buf = argument0;var data = argument1;var size = buffer_get_size(buf);var pos = buffer_tell(buf);var need = pos + next;if (size < need) { do size *= 2 until (size >= need); buffer_resize(buf, size);}buffer_copy(data, start, next, buf, pos);buffer_seek(buf, buffer_seek_relative, next);

This writes a section of one buffer to other buffer, and is used extensively here.

Then you can add the script itself, called json_beautify:

/// json_beautify(json_string)// initialization// in old versions of GMS, you'd have this ran separately instead.// in GMS2 it'd need to be @"..." instead of just "..."gml_pragma("global", "global.g_json_beautify_fb = buffer_create(1024, buffer_fast, 1);global.g_json_beautify_rb = buffer_create(1024, buffer_grow, 1);");var src = argument0;// copy text to string buffer:var rb = global.g_json_beautify_rb;buffer_seek(rb, buffer_seek_start, 0);buffer_write(rb, buffer_string, src);var size = buffer_tell(rb) - 1;var rbsize = buffer_get_size(rb);// then copy it to "fast" input buffer for peeking:var fb = global.g_json_beautify_fb;if (buffer_get_size(fb) < size) buffer_resize(fb, size);buffer_copy(rb, 0, size, fb, 0);buffer_seek(rb, buffer_seek_start, 0);//var rbpos = 0; // writing position in output buffervar start = 0; // start offset in input buffervar pos = 0; // reading position in input buffervar next; // number of bytes to be copiedvar need;var nest = 0;while (pos < size) { var c = buffer_peek(fb, pos++, buffer_u8); switch (c) { case 9: case 10: case 13: case 32: // `\t\n\r ` buffer_write_slice(rb, fb, start, pos - 1); // skip over trailing whitespace: while (pos < size) { switch (buffer_peek(fb, pos, buffer_u8)) { case 9: case 10: case 13: case 32: pos += 1; continue; // default -> break } break; } start = pos; break; case 34: // `"` while (pos < size) { switch (buffer_peek(fb, pos++, buffer_u8)) { case 92: pos++; continue; // `\"` case 34: break; // `"` -> break default: continue; // else } break; } break; case ord("["): case ord("{"): buffer_write_slice(rb, fb, start, pos); // skip over trailing whitespace: while (pos < size) { switch (buffer_peek(fb, pos, buffer_u8)) { case 9: case 10: case 13: case 32: pos += 1; continue; // default -> break } break; } // indent or contract `[]`/`{}` c = buffer_peek(fb, pos, buffer_u8); switch (c) { case ord("]"): case ord("}"): // `[]` or `{}` buffer_write(rb, buffer_u8, c); pos += 1; break; default: // `[\r\n\t buffer_write(rb, buffer_u16, 2573); // `\r\n` repeat (++nest) buffer_write(rb, buffer_u8, 9); // `\t` } start = pos; break; case ord("]"): case ord("}"): buffer_write_slice(rb, fb, start, pos - 1); buffer_write(rb, buffer_u16, 2573); // `\r\n` repeat (--nest) buffer_write(rb, buffer_u8, 9); // `\t` buffer_write(rb, buffer_u8, c); start = pos; break; case ord(","): buffer_write_slice(rb, fb, start, pos); buffer_write(rb, buffer_u16, 2573); // `\r\n` repeat (nest) buffer_write(rb, buffer_u8, 9); // `\t` start = pos; break; case ord(":"): if (buffer_peek(fb, pos, buffer_u8) != ord(" ")) { buffer_write_slice(rb, fb, start, pos); buffer_write(rb, buffer_u8, ord(" ")); start = pos; } else pos += 1; break; default: if (c >= ord("0") && c <= ord("9")) { // `0`..`9` var pre = true; // whether reading pre-dot or not var till = pos - 1; // index at which meaningful part of the number ends while (pos < size) { c = buffer_peek(fb, pos, buffer_u8); if (c == ord(".")) { pre = false; // whether reading pre-dot or not pos += 1; // index at which meaningful part of the number ends } 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 = pos; pos += 1; } else break; } if (till < pos) { // flush if number can be shortened buffer_write_slice(rb, fb, start, till + 1); start = pos; } } }}if (start == 0) return src; // source string was unchangedbuffer_write_slice(rb, fb, start, pos);buffer_write(rb, buffer_u8, 0); // terminating bytebuffer_seek(rb, buffer_seek_start, 0);return buffer_read(rb, buffer_string);

and then use it on JSON strings you get from json_encode or wherever else.

As an additional note, this uses tabs for indentation, but you can have it to use spaces by changing buffer_write(rb, buffer_u8, 9); to buffer_write(rb, buffer_text, " "), for example.

Have fun !

Related posts:

  • GameMaker: Minifying JSON
  • GameMaker: Fixing the JSON functions
  • GameMaker: Recursive folder copying
  • GameMaker: Taking screenshot of area larger than screen
  • GameMaker: Executable “self-destruction”
GameMaker: Beautifying/pretty-printing JSON (2024)
Top Articles
Latest Posts
Article information

Author: Kareem Mueller DO

Last Updated:

Views: 6157

Rating: 4.6 / 5 (66 voted)

Reviews: 81% of readers found this page helpful

Author information

Name: Kareem Mueller DO

Birthday: 1997-01-04

Address: Apt. 156 12935 Runolfsdottir Mission, Greenfort, MN 74384-6749

Phone: +16704982844747

Job: Corporate Administration Planner

Hobby: Mountain biking, Jewelry making, Stone skipping, Lacemaking, Knife making, Scrapbooking, Letterboxing

Introduction: My name is Kareem Mueller DO, I am a vivacious, super, thoughtful, excited, handsome, beautiful, combative person who loves writing and wants to share my knowledge and understanding with you.