# Getting Started with the fson Library
This tutorial walks through the typical lifecycle of an FSON file: load,
inspect, modify, save — without losing a single comment. Everything lives
in the namespace `fedem::fson`; build instructions are in
[`integration.md`](integration.md), the file format itself in
[`notation.md`](notation.md), and the complete API in
[`reference_manual.md`](reference_manual.md).
Suppose `config.json` contains:
```
// service configuration
{
database: {
host: "localhost", // dev default
port: 5432
},
retries: [1, 2, 5],
--debug: true
}
```
## 1. Loading a file
```cpp
#include <iostream>
#include "fson/Fson.hh"
using namespace fedem::fson;
int main( )
{
Fson::LoadResult result = Fson::load( "config.json" );
for( auto const& error : result.errors )
std::cerr << error.filename << ':' << error.line << ':' << error.col
<< ": " << error.message << '\n';
if( !result.ok( ) ) // ok() tolerates warnings, not errors
return 1;
Document& document = *result.document;
// ...
}
```
`Fson::load()` never throws. The returned document is null only when the
file could not be opened; parse errors still produce a (partial) document
plus the error list, so you can report precisely what is wrong. Note the
asymmetry: a missing file adds *no* entry to `result.errors` — that loop
above prints nothing for it — so `result.document == nullptr` (or `!ok()`)
is the only signal for "could not open", not an empty error list.
## 2. Reading values
The quickest way is the dotted-path API with typed getters; the second
argument is the fallback for a missing path *or* a type mismatch:
```cpp
std::string host = document.getString ( "database.host", "localhost" );
long long port = document.getInteger( "database.port", 5432 );
long long r0 = document.getInteger( "retries[0]", 0 ); // array element
bool dbg = document.getBoolean( "debug", false ); // false: disabled!
```
Note the last line: `--debug` is a *disabled* member, so the query API
treats it as absent — exactly what the `--` prefix is for.
For structured access, walk the model:
```cpp
Object& root = document.getRoot( );
if( Value* value = root.findPath( "database.port" ) )
if( Number* number = value->asNumber( ) )
std::cout << number->asInteger( ) << '\n';
```
`as*()` casts return `nullptr` on a type mismatch; `at()`/`atPath()` throw
`std::out_of_range` when something is missing.
A string can also be written as a multi-line `|`-block, which reads back
as one value with newlines:
```
banner: |Welcome
|to the app
```
`document.getString( "banner" )` yields `"Welcome\nto the app\n"` — each
`|`-line contributes a trailing newline (the last included). End a line
with `\` to join the next without a break. On save the block form is kept
(in the FSON dialect); see `docs/notation.md` for the full rules.
## 3. Modifying the document
```cpp
#include "fson/Number.hh"
#include "fson/String.hh"
// replace a value
document.setPath( "database.host", std::make_unique< String >( "db1" ) );
// create nested members in one go (missing objects are created)
document.setPath( "database.pool.size", std::make_unique< Number >( 8LL ) );
// remove a member or an array element
document.removePath( "retries[1]" );
// switch members off and on — the data is kept either way
document.getRoot( ).disable( "retries" );
document.getRoot( ).at( "database" ).asObject( )->enable( "user" );
```
`setPath()` never grows arrays and never resurrects a disabled member; a
failed call leaves the document untouched.
## 4. Saving
```cpp
if( !Fson::save( document, "config.json" ) )
std::cerr << "cannot write config.json\n";
```
The writer pretty-prints (2-space indent, one member per line) and writes
back **everything** the parser captured: comments in all positions, member
order, quoted-vs-bare key form, `--` markers, and numbers exactly as they
were written (`2.50` does not become `2.5`). Layout itself is not one of
the preserved things: arrays are always re-wrapped one element per line
(`[1, 2, 5]` becomes three lines), and the writer inserts whatever commas
that form needs — an edit earlier in an object can add a trailing comma to
a line untouched since loading.
## 5. Sharing defaults with `%include`
A file can pull defaults from another with a `%include` directive:
```
// service.fson
{
%include "base.fson", // base's members fill in as defaults
timeout: 60 // ... but this host value wins over base's
}
```
`Fson::load` resolves it automatically — the query view shows the merged
result (host over base, shallow), while `save` writes only the host file
back (the `%include` line is kept; base's values are not copied in):
```cpp
auto result = Fson::load( "service.fson" );
result.document->getInteger( "timeout", 0 ); // 60 (host)
result.document->getInteger( "retries", 0 ); // from base.fson
// where did a value come from?
result.document->isLocal( "timeout" ); // true (host)
auto p = result.document->provenanceOf( "retries" );
// p->sourceFile → canonical path of base.fson; p->localPath → "retries"
// files needed to resolve this document — host + every include, sorted.
// This is the watch list for reload:
for( auto const& file : result.dependencySet ) /* ... */;
// or, without keeping the document at all:
std::set< std::string > files = Fson::dependenciesOf( "service.fson" );
```
Use `%include "path" as name` to bind another file's whole root under one
key instead of merging. Prefix with `--` (`--%include "…"`) to keep a
directive but skip it. Cycles and missing targets are reported as errors,
never thrown.
To reload only when something actually changed, snapshot after loading and
probe with `isStale` (fson runs no watcher thread — you drive it):
```cpp
auto snap = Fson::snapshot( "service.fson" );
// ... on a timer or an OS file event ...
if( Fson::isStale( "service.fson", snap, Fson::StaleMode::ContentHash ) )
result = Fson::load( "service.fson" ); // and re-snapshot
```
`StaleMode::MtimeSize` is cheaper (no re-read) but treats a bare touch as
a change; `ContentHash` re-reads and ignores touches with identical bytes.
Either mode also catches a deleted dependency or a new `%include`.
## 6. Checking files from the command line
The same load–report–rewrite cycle is available as a tool:
```sh
fson-check config.json # report errors, pretty-print to stdout
fson-check -i config.json # reformat in place (only when error-free)
fson-check --output out.json in.json # write the result elsewhere
fson-check --json config.json # convert to plain JSON (for jq, etc.)
fson-check --json --keep config.json # ... keeping the '--' members as data
fson-check --json5 config.json # convert to JSON5 (keeps comments and bare keys)
fson-check --bare config.json # unquote keys where the grammar allows
```
Diagnostics always go to stderr; the exit status is 0 (clean), 1 (parse
errors) or 2 (usage/IO failure). Try it on the files in
[`examples/`](../examples/).