Converting Lua Objects to Strings

Introduction

If you have loaded the scribe module as:

local scribe = require 'scribe'

Then, you have several functions for turning Lua objects into strings.

The methods vary by the way they present Lua tables. Assuming you haven’t altered the defaults as outlined in scribe.options then:

Method Returned String
scribe.inline(obj) A one-line string.
Arrays are delimited using square brackets [ ... ].
Other tables are delimited using curly braces { ... }.
scribe.pretty(obj) A “pretty”, generally multiline, string for tables.
Arrays are delimited using square brackets [ ... ].
Other tables are delimited using curly braces { ... }.
Simple tables & arrays with no nesting are shown on one line.
scribe.classic(obj) A “classic” multiline format where elements are on their line.
Tables and arrays are delimited using curly braces { ... }.
scribe.alt(obj) An alternate multiline string for tables without delimiters.
Table structure is shown by indentation.
scribe.json(obj) A multiline JSON string for tables.
scribe.inline_json(obj) A compact one-line JSON string for tables.
scribe.debug(obj) A string that exposes the structure of a table.
scribe.scribe(obj, opts) Parent function used by the other methods.
Converts obj to a string based on the opts formatting table.

These methods are drop-in replacements for the standard Lua tostring function. They return a reasonable string for any Lua object.

The methods differ in how they format Lua tables as strings. The first nine methods call on the final scribe.scribe(obj, options) with different values for the options argument. See the Standard Options page for details on the specific formatting values.

Instead of calling scribe.scribe(obj, opts) you can call scribe(obj, opts). The second argument is optional. If it’s missing, we pass a default set of formatting options. Out of the box, those default options are the same as those used to produce one-line strings.
The only complex native Lua type is a table. In Lua, arrays are tables where the keys are successive integers starting at 1. Scribe has several output methods that mark arrays with different delimiters—by default, [ ... ] versus { ... }.

Optional Overrides

The scribe method can also take an extra optional argument overrides:

scribe(obj, options, overrides)

The options table is the primary set of formatting options used to display table objects. The optional overrides argument can tweak any field in that main set. If present, it should be a table. We extract any valid scribe.options field from it instead of the one in options.

All the other methods above can also be passed overrides as an optional argument.

For example:

scribe.inline(obj, overrides)

is the same as the call:

scribe(obj, scribe.options.inline, overrides)

Suppose you wish to delimit inline arrays with parentheses. You can achieve that by passing some overrides in a table to scribe.inline:

local my_options = { array_begin = '(', array_end = ')'}
local fruits = {'Apple', 'Pear', 'Banana'}
print(scribe.inline(fruits, my_options))

This returns the string ( "Apple", "Pear", "Banana" ).

On return, you can now use my_options as a completely independent set of formatting options. It will have been filled out with all the needed fields from, in this case, scribe.options.inline. To see that we can pretty print it!

putln("my_options: %2T", my_options)`

yields:

my_options: {
    COMPLETE = true,
1    array_begin = "(",
    array_end = ")",
    comparator = <function>,
    indent = "",
2    inline_size = inf,
    inline_spacer = " ",
    key_begin = "",
    key_end = " = ",
    path_begin = "<",
    path_end = ">",
    path_root = "table",
    path_sep = ".",
    sep = ",",
    show_indices = false,
    table_begin = "{",
    table_end = "}",
    use_metatable = true
}
1
The array_begin and array_end fields have been set to ( and ) respectively.
2
inf is a special value that means “infinity” accessible in Lua via math.huge.

This means you could now completely replace scribe.options.inline if you wish by setting:

scribe.options.inline = my_options

Then putln("Fruits: %t", fruits) returns Fruits: ( "Apple", "Pear", "Banana" ).

On return, the overrides argument will be expanded to include all the non-customised fields from the options table, which it overrides.

Optional Name

The first seven methods above all can take an extra optional string argument. If present, we assume that it is a name you wish to embed in the returned string.

For example, the whole calling sequence for scribe.inline is:

scribe.inline(obj, overrides, name)

The final two arguments are optional.

Assuming overrides is a table and name is a string, then any of the following calls are valid:

scribe.inline(obj)
scribe.inline(obj, overrides)
scribe.inline(obj, name)
scribe.inline(obj, overrides, name)
scribe.inline(obj, name, overrides)

For the non-JSON methods, if the name argument is present, it just gets prepended to the returned string.

print(scribe.inline(fruits, "Fruits: "))

returns the string Fruits: [ "Apple", "Pear", "Banana" ].

For the two JSON conversions, we assume you wish to “embed” the name in a JSON-like manner:

print(scribe.json(fruits, "Fruits"))

returns

{"Fruits": [
    "Apple",
    "Pear",
    "Banana"
]}

Examples: Array

We look at how each of those methods outputs a simple array.

Simple Array Input

local fruits = {'Apple', 'Pear', 'Banana'}
print(scribe.inline(fruits,        'Inline Format:\n'),        '\n')
print(scribe.pretty(fruits,        'Pretty Format:\n'),        '\n')
print(scribe.classic(fruits,       'Classic Format:\n'),       '\n')
print(scribe.alt(fruits,           'Alt Format:\n'),           '\n')
print(scribe.inline_json(fruits,   'Inline-JSON-Format'),      '\n')
print(scribe.json(fruits,          'JSON-Format'),             '\n')

Simple Array Output

Inline Format:
1[ "Apple", "Pear", "Banana" ]

Pretty Format:
2[ "Apple", "Pear", "Banana" ]

3Classic Format:
{
    "Apple",
    "Pear",
    "Banana"
}

4Alt Format:
    "Apple", "Pear", "Banana",
"Apple",
"Pear",
"Banana"

5{"Inline-JSON-Format": ["Apple","Pear","Banana"]}

6{"JSON-Format": [
    "Apple",
    "Pear",
    "Banana"
]}
1
The table is printed on a single line using square bracket delimiters because fruits is an array.
2
The table is “simple” with no nested sub-tables, so the pretty output is still on a single line.
3
The “classic” format puts everything on its line and uses brace delimiters and indentation.
4
The alternate multiline output doesn’t use table delimiters and doesn’t need indentation in this simple case.
5
JSON always uses square braces to delimit arrays.
The inline version avoids white space as much as possible.
6
This is a classic JSON multiline string for an array of values.

Example: Linked List

Let’s look at how some of the same methods output a “linked list”:

Linked List Input

local list = {p1 = {name = 'Alice'}, p2 = {name = 'Maria'}}
list.p1.next = list.p2
list.p2.prev = list.p1

print(scribe.inline(list,  'Inline Format:\n'), '\n')
print(scribe.pretty(list,  'Pretty Format:\n'), '\n')
print(scribe.alt(list,     'Alt Format:\n'),    '\n')
print(scribe.classic(list, 'Classic Format:\n'))

Linked List Output

Inline Format:
1{ p1 = { name = "Alice", next = <p2> }, p2 = { name = "Maria", prev = <p1> } }

Pretty Format:
{
    p1 = {
        name = "Alice",
        next = <p2>
    },
    p2 = {
        name = "Maria",
        prev = <p1>
    }
}

Alt Format:
p1:
    name: "Alice",
    next: <p2>,
p2:
    name: "Maria",
    prev: <p1>

Classic Format:
{
    p1 = {
        name = "Alice",
        next = <p2>
    },
    p2 = {
        name = "Maria",
        prev = <p1>
    }
}
1
The table is printed on a single line. Path references show shared tables.

Another Example

We look at the output for a doubly linked list.

Doubly Linked List Input

local a = { node = 'Thomas', payload = 10 }
local b = { node = 'Harold', payload = 20 }
local c = { node = 'Sloane', payload = 30 }
local d = { node = 'Daphne', payload = 40 }

a.next, b.next, c.next, d.next = b, c, d, d
a.prev, b.prev, c.prev, d.prev = a, a, b, c
local linked_list = { a, b, c, d }

print(scribe.pretty(linked_list, "Pretty Format:\n"), '\n')
print(scribe.alt(linked_list,    "Alt Format:\n"), '\n')

Doubly Linked List Output

Pretty Format:
[
    1 = { next = <2>, node = "Thomas", payload = 10, prev = <1> },
    2 = { next = <3>, node = "Harold", payload = 20, prev = <1> },
    3 = { next = <4>, node = "Sloane", payload = 30, prev = <2> },
    4 = { next = <4>, node = "Daphne", payload = 40, prev = <3> }
]


Alt Format:
1:
    next: <2>,
    node: "Thomas",
    payload: 10,
    prev: <1>,
2:
    next: <3>,
    node: "Harold",
    payload: 20,
    prev: <1>,
3:
    next: <4>,
    node: "Sloane",
    payload: 30,
    prev: <2>,
4:
    next: <4>,
    node: "Daphne",
    payload: 40,
    prev: <3>

Note that the array elements are shown inline in the pretty output. Those elements are simple tables with no nested sub-tables; the embedded next and prev fields reference other tables and do not count as sub-tables.

The alt output shows the array elements on separate lines.

Because we have cycles and references, we show all the array indices.

See Also

Formatting Options
Standard Options
Customising Options
Output Methods
Turning the Tables …

Back to top