How does the Path type work in EK9 and what is it for?
← Getting Started · Ref: Q42
IMPORTANT: Path in EK9 is NOT a file system path. It is a data navigation path for traversing object graphs like JSON structures. If you need file system paths, use FileSystemPath instead. This is a critical distinction that developers from other languages often miss.
PATH IS FOR DATA NAVIGATION
Path uses the $? prefix to create compile-time checked navigation expressions for object graphs (JSON, nested records, data structures):
simplePath <- $?.aKey nested <- $?.some.path.to.value fromArray <- $?[0].a-field multiDim <- $?.data[2][1].multi-dimensional.array complex <- $?.some.path.inc[0].array
FILESYSTEMPATH IS FOR FILES
FileSystemPath is the separate type for file and directory operations:
filePath <- FileSystemPath("/home/user/file.txt")
It has methods like parent(), fileName(), extension(), exists(). Path has none of these because it navigates data, not file systems.
THE $? PREFIX
The path literal starts with $? followed by property names and array indices:
Properties: $?.propertyName (dot-separated) Arrays: $?[index] (bracket notation) Mixed: $?.store.book[0].title Multi-dimensional: $?[2][1].field Hyphenated keys: $?[0].a-field
The compiler validates the syntax at compile time. Unlike Java's JsonPath strings or Python's dict key chains, typos and structural errors are caught before your code runs.
THE $ OPERATOR FAMILY
EK9 has three related operators that all use $ as a visual anchor but serve completely different purposes:
$value to-string conversion (any type to String) $$record to-JSON serialization (record/class to JSON) $?.path data path literal (compile-time checked navigation)
One $ is simple conversion, two $$ is structured output, $? is structured input navigation. This visual pattern makes the intent immediately recognizable in code.
ESCAPING $ IN INTERPOLATED STRINGS
Since $ triggers interpolation in backtick strings, use \$ to include a literal dollar sign:
stdout.println(`\$ to-string: ${stringRep}`) stdout.println(`\$\$ to-JSON: ${jsonOfMe}`) stdout.println(`\$? path: ${navPath}`)
In normal double-quoted strings, $ does not need escaping. In backtick interpolation, $ and backtick must be escaped with backslash.
PATH CONCATENATION
The + operator joins paths:
basePath <- $?.api extended <- basePath + $?.users
You can also append strings:
withVersion <- basePath + "/v2"
PATH OPERATORS
contains: check if path contains a substring fullPath contains "store" true/false matches: match against a regular expression path matches /user.*/ true/false length: character count of the path ==, <>, <, >, <=, >=, <=>: comparison operators
WHY A BUILT-IN PATH TYPE?
Modern applications constantly navigate data structures: JSON API responses, configuration trees, nested records. In Java, you use string-based JsonPath ("$.store.book[0].title") with no compile-time checking. In JavaScript, optional chaining (obj?.store?.book?.[0]?.title) is concise but still unchecked. In Python, nested dict access (data['store']['book'][0]) throws KeyError at runtime.
EK9 makes data navigation a first-class type. Paths can be stored in variables, passed as parameters, composed programmatically, and validated at compile time. This is the same philosophy as RegEx being built-in rather than a library: if something is used everywhere, it should be part of the language with compile-time safety.
WHY SEPARATE FROM FILESYSTEMPATH?
Because they solve completely different problems:
Path: navigates IN-MEMORY data structures (JSON, object graphs) FileSystemPath: navigates ON-DISK file system hierarchies
Mixing these concepts (as most languages do with a single 'path' type or string) leads to confusion. A JSON path $?.users[0].name has nothing to do with /home/users/name.txt. Different types, different operations, different safety requirements.
See Q43 (How do I escape characters in EK9 string interpolation?) for the complete rules on escaping $ in backtick strings. See Q37 (How do I work with strings in EK9?) for string interpolation basics.
Use 'ek9 -h Path' and 'ek9 -h FileSystemPath' to see the full APIs.
Example
defines module qa.path defines record CustomerDetail firstName <- String() lastName <- String() default CustomerDetail() CustomerDetail() -> firstName as String lastName as String this.firstName :=: firstName this.lastName :=: lastName default operator $$ default operator ? defines program PathDemo() stdout <- Stdout() // === PATH LITERALS === // $? prefix creates a compile-time checked path simplePath <- $?.aKey stdout.println(`Simple: ${simplePath}`) // Nested property access nested <- $?.some.path.to.value stdout.println(`Nested: ${nested}`) // Array indexing firstElement <- $?[0] stdout.println(`First element: ${firstElement}`) // Property from array element fromArray <- $?[0].a-field stdout.println(`From array: ${fromArray}`) // Multi-dimensional array access multiDim <- $?.data[2][1].multi-dimensional.array stdout.println(`Multi-dim: ${multiDim}`) // Complex path complex <- $?.some.path.inc[0].array stdout.println(`Complex: ${complex}`) // === DISTINGUISH FROM $ AND $$ === // $value is the to-string operator anInt <- 42 stringRep <- $anInt stdout.println(`\$ to-string: ${stringRep}`) // $$record is the to-JSON operator me <- CustomerDetail(firstName: "Steve", lastName: "Limb") jsonOfMe <- $$me stdout.println(`\$\$ to-JSON: ${jsonOfMe}`) // $?.path is the path navigation literal navPath <- $?.store.book[0].title stdout.println(`\$? path: ${navPath}`) // === PATH CONCATENATION === basePath <- $?.api extended <- basePath + $?.users stdout.println(`Concatenated: ${extended}`) // Append string withString <- basePath + "/v2" stdout.println(`With string: ${withString}`) // === PATH OPERATORS === // Length stdout.println(`Length: ${length nested}`) // Comparison path1 <- $?.alpha path2 <- $?.beta require path1 <> path2 require path1 < path2 stdout.println(`alpha <=> beta: ${path1 <=> path2}`) // Contains fullPath <- $?.store.book[0].title containsResult <- fullPath contains "store" stdout.println(`Contains 'store': ${containsResult}`) // Matches with RegEx matchPath <- $?.users matchResult <- matchPath matches /user.*/ stdout.println(`Matches /user.*/: ${matchResult}`) // === HASHCODE === hash <- #? simplePath stdout.println(`Hash: ${hash}`) // === UNSET PATH === unsetPath <- Path() require ~unsetPath? stdout.println(`Unset isSet: ${unsetPath?}`) // === COPY === copied <- Path() copied :=: nested require copied == nested stdout.println(`Copied: ${copied}`)
Common mistakes
E08180 — Record fields must be initialised at declaration. Declaring fields without initialisation triggers E08180. Use inline initialisation like 'String()' to create an unset-but-initialised field. See ek9 -h E08180 for details.
Incorrect:
firstName as String lastName as String
Correct:
firstName <- String() lastName <- String()
Other ways to ask this
- What is the $? path literal in EK9?
- How do I navigate JSON data structures in EK9?
- What is the difference between Path and FileSystemPath in EK9?
- How do the $, $$, and $? operators differ in EK9?
Coming from another language?
Java: JsonPath library with string expressions $.store.book[0].title, no compile-time checking, runtime exceptions on invalid paths, java.nio.file.Path for file system. JavaScript: native optional chaining obj?.store?.book?.[0], jq for CLI JSON queries, no compile-time validation, fs.path for files. Python: nested dict access data['store']['book'][0] with KeyError, jsonpath-ng library, pathlib.Path for files. Rust: serde_json Value indexing value["store"]["book"][0], jsonpath-rust crate, std::path::Path for files. Go: encoding/json with map[string]interface{} casting, gjson library for JSON paths, filepath package for files. C#: System.Text.Json JsonElement navigation, JsonPath.Net library, System.IO.Path for files. EK9: native $?.path literal with compile-time validation, Path type for data navigation, FileSystemPath type for files, clear separation of concerns.
Keywords: filesystempath, escape, data, dollar, navigation, object, start, compile, beginner, migrate, interpolation, path, graph, literal, property, array, json, first, file, intro