There are lots of different tactics, but a good simple starting point when thinking about compression is RLE - run-length encoding.
The idea is that if you have a set of data with repeating values, then instead of writing them as-is, you might output a special symbol to indicate that the next value is repeated, then you might output the symbol itself, then you might output a number indicating how many times it's to be repeated.
The values you output can be in whatever format you like, so long as you're able to read them back later.
A very simple example, using a table filled with values (though you don't even need to use a table!):
{ "block", "dirt", "air", "air", "air", "dirt", "stone", "stone" }
Here we've got a consecutive run of three "air"s in a row, and a consecutive run of two "stone"s in a row. So to condense that, we might do something like:
{ "block", "dirt", {"air", 3}, "dirt", {"stone", 2} }
You can hopefully see how trivial it would be to write the code which would turn the original table into this, and the code to turn this back into the original form.