Skip to content

Records (objects) contain key-value pairs. This guide shows you how to work with records — accessing fields, extracting keys, combining fragments, and transforming values.

Get values using dot notation or brackets:

from {
user: {
name: "Alice",
age: 30,
address: {
city: "NYC",
zip: "10001"
}
}
}
name = user.name
city = user.address.city
zip = user["address"]["zip"]
has_email = user.has("email")
{
user: {
name: "Alice",
age: 30,
address: {city: "NYC", zip: "10001"}
},
name: "Alice",
city: "NYC",
zip: "10001",
has_email: false
}

Extract field names and values:

from {
config: {
host: "localhost",
port: 8080,
ssl: true
}
}
field_names = config.keys()
// Note: values() function is not available
num_fields = config.keys().length()
{
config: {host: "localhost", port: 8080, ssl: true},
field_names: ["host", "port", "ssl"],
num_fields: 3
}

Use the spread operator ... to combine records. Spread keeps the resulting record visible where you construct it, and lets you mix existing fragments with literals or computed fields. Later fields overwrite earlier fields, so put defaults first and overrides last:

from {
defaults: {host: "localhost", port: 80, ssl: false},
custom: {port: 8080, ssl: true}
}
service = {
...defaults,
...custom,
debug: true,
}
{
defaults: {host: "localhost", port: 80, ssl: false},
custom: {port: 8080, ssl: true},
service: {
host: "localhost",
port: 8080,
ssl: true,
debug: true
}
}

Use optional access when a record fragment may be missing. If the spread expression evaluates to null, it contributes no fields, so an explicit else {} fallback is not necessary:

from {account: {id: 1, name: "Alice"}}
user = {
...account,
...account.profile?,
active: true,
}
{
account: {
id: 1,
name: "Alice",
},
user: {
id: 1,
name: "Alice",
active: true,
},
}

The fnmerge function returns the same result for two records, including null fragments, but prefer spread in transformation code when you construct the resulting record.

Note: The map_values function is not available in TQL. To transform record values, you would need to reconstruct the record with transformed values manually:

from {
prices: {
apple: 1.50,
banana: 0.75,
orange: 2.00
}
}
// Manual transformation example:
with_tax = {
apple: prices.apple * 1.1,
banana: prices.banana * 1.1,
orange: prices.orange * 1.1
}

Keep explicit fields by constructing a new record. Use fnselect_matching and fndrop_matching when field names follow a pattern:

from {
user: {
id: 123,
name: "Alice",
email: "alice@example.com",
password: "secret",
api_key: "xyz123"
}
}
public_info = {
id: user.id,
name: user.name,
email: user.email
}
contact = {
name: user.name,
email: user.email
}
credentials = user.select_matching("(password|api_key)$")
safe_user = user.drop_matching("(password|api_key)$")
{
user: {
id: 123,
name: "Alice",
email: "alice@example.com",
password: "secret",
api_key: "xyz123"
},
public_info: {
id: 123,
name: "Alice",
email: "alice@example.com"
},
contact: {
name: "Alice",
email: "alice@example.com"
},
credentials: {
password: "secret",
api_key: "xyz123"
},
safe_user: {
id: 123,
name: "Alice",
email: "alice@example.com"
}
}

The matching functions inspect only top-level field names on the record you call them on. They don’t recurse into nested records.

Last updated: