JSON Config
Parse JSON, stringify it, and model configuration data with a typed struct.
Core concepts
std.json, json.parse, json.stringify, struct config, project layout
How the learner should approach this project
Do not paste the complete program immediately. Create the file, type the first step, run vpp check, and then run the program when a complete entry point exists. Read the explanation before looking at the snippet. After each step, predict what the new code should do. This turns the page into a lesson rather than a code dump.
Step 1: Import std.json
The standard JSON module provides parse and stringify operations. Imports must appear before functions and types.
After typing the snippet, identify the new names introduced by the step. Ask what each name represents, what type the compiler should infer or check, and what value should exist after the code runs.
import std.jsonStep 2: Define AppConfig
The struct gives the configuration a typed shape. name and version are strings, while debug is a boolean. This is the point where unstructured configuration data becomes a clearly named application model.
Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.
struct AppConfig {
name: string
version: string
debug: bool
}Step 3: Parse and stringify JSON
parse receives raw JSON text and produces a JSON document value. stringify converts that document back into text.
This function demonstrates a clean boundary between raw configuration text and structured processing.
Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.
fn parse_config(raw: string) -> string {
let doc = json.parse(raw)
return json.stringify(doc)
}Step 4: Use JSON and typed configuration together
main starts with a JSON string containing name, version, and debug. It passes the raw text through parse_config and prints the resulting JSON. It then creates a typed AppConfig value and accesses its fields directly.
Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.
fn main() -> int {
let raw = "{"name":"v++","version":"1.0.0","debug":false}"
print(parse_config(raw))
let cfg = AppConfig {
name: "v++"
version: "1.0.0"
debug: true
}
print(cfg.name)
print(cfg.version)
return 0
}Complete source
import std.json
struct AppConfig {
name: string
version: string
debug: bool
}
fn parse_config(raw: string) -> string {
let doc = json.parse(raw)
return json.stringify(doc)
}
fn main() -> int {
let raw = "{"name":"v++","version":"1.0.0","debug":false}"
print(parse_config(raw))
let cfg = AppConfig {
name: "v++"
version: "1.0.0"
debug: true
}
print(cfg.name)
print(cfg.version)
return 0
}Expected behavior
The complete program should produce the following output when run with the command shown below. Exact formatting should follow the current V++ runtime.
{"name":"v++","version":"1.0.0","debug":false}
v++
1.0.0
Run the project
vpp run projects/20-json-config/main.vppWhat the learner should understand after this project
JSON is unstructured text at the boundary, while a struct provides a typed model inside the program. Keeping those roles separate makes configuration easier to reason about.
Common mistakes to teach
- Malformed JSON text
- Using a JSON field as if it were already a typed struct field
- Changing the struct without updating construction sites
Practice extension
Add a port field to AppConfig as an int and update the example configuration.
A strong learner should be able to explain the program without looking at the code, rebuild the core idea from memory, and make the practice change without copying a solution.
Full program
import std.json
struct AppConfig {
name: string
version: string
debug: bool
}
fn parse_config(raw: string) -> string {
let doc = json.parse(raw)
return json.stringify(doc)
}
fn main() -> int {
let raw = "{"name":"v++","version":"1.0.0","debug":false}"
print(parse_config(raw))
let cfg = AppConfig {
name: "v++"
version: "1.0.0"
debug: true
}
print(cfg.name)
print(cfg.version)
return 0
}