:title
8. Dynamic values

:content
A `dval` holds data whose shape is known only at run time. JSON, YAML, request data, task props, and unit calls use `dval` values.

## Read decoded data

:example capy render
function RENDER()
{
	var profile := json_decode("{\"name\":\"Ada\"}")
	print(profile.name, "\n")
}

:output
Ada

:content
`json_decode` returns a `dval`. Member syntax reads a map key. Bracket syntax also works:

:example capy render
function RENDER()
{
	var profile := { name: "Ada" }
	print(profile["name"], "\n")
}

:content

Use brackets for a dynamic key, a key that is not an identifier, or a list index.

## Missing data

A missing read returns `none` instead of changing the map or trapping:

:example capy render
function RENDER()
{
	var profile := { name: "Ada" }
	var nickname := profile.nickname
	if nickname?
	{
		print(nickname)
	}
	else
	{
		print("guest")
	}
}

:content

`value?` is false only when the `dval` is `none`. An empty string, `false`, an empty map, and an empty list are present values.

A key can exist and contain `none`. Use `has(profile, "nickname")` when that distinction matters.

## Convert a scalar

Scalar constructors convert a `dval` to a static Capy type:

:example capy render
function RENDER()
{
	var profile := { name: "Ada" }
	var name := string(profile.name)
	var visits := s32(profile.visits)
	print(name, " ", visits, "\n")
}

:content

The options map can set `fallback`. Capy uses it when the converted text is empty or cannot convert to the requested type. Omit the map when the type default is correct.

Use `has(profile, "name")` before `get(profile, "name")` when a missing value is an error.

## Create dynamic data

A map literal creates a `dval` map:

:example capy render
function RENDER()
{
	var user := {
		name: "Ada",
		active: true
	}
	var empty := {}
	print(user.name, " ", empty?, "\n")
}

:content

A bare list is a typed array. Wrap it with `dval` when it must be a dynamic list. `length(value)` returns the number of entries in a `dval` map or list. It traps if the `dval` is a scalar:

:example capy render
function RENDER()
{
	var tags := dval(["math", "logic"])
	print(tags[0], "\n")
}

:content

## One object, many names

A `dval` is an object. Assignment, parameters, return values, captures, and member reads all refer to the same object. Nothing is copied:

:example capy render
function set_email(profile : dval)
{
	profile.contact.email = "ada@example.test"
}

function RENDER()
{
	var profile := {}
	var alias := profile
	set_email(alias)
	print(profile.contact.email, "\n")
}

:content

`alias` and `profile` are two names for one object, so the change made through `set_email` shows in `profile`. A parameter name cannot be pointed at another object, so `profile = {}` inside `set_email` is a compile error. `profile.contact = {}` is fine: it changes the object.

Reading a member gives you the child object itself, not a copy:

:example capy render
function RENDER()
{
	var profile := { contact: { email: "ada@example.test" } }
	var contact := profile.contact
	contact.email = "grace@example.test"
	print(profile.contact.email, "\n")
}

:output
grace@example.test

:content

Storing a dval in another dval, by assignment, `set`, `push`, or a map literal, stores the object itself:

:example capy render
function RENDER()
{
	var contact := { email: "ada@example.test" }
	var profile := { contact: contact }
	contact.email = "grace@example.test"
	print(profile.contact.email, "\n")
}

:output
grace@example.test

:content

`profile.contact.email = ...` creates the maps on the way when they are missing. A list does not grow this way: writing to an index that does not exist is an error, use `push`.

`set`, `push`, `pop`, `delete`, `clear`, and `get_or_create` change the object and return it, so `profile = set(profile, "active", true)` and `set(profile, "active", true)` do the same thing.

`clone(value)` is the one way to get an independent copy:

:example capy render
function RENDER()
{
	var profile := { name: "Ada" }
	var copy := clone(profile)
	copy.name = "Grace"
	print(profile.name, " ", copy.name, "\n")
}

:output
Ada Grace

:content

Within a request this holds everywhere: component props, the input and result of an exported function, and the request value itself are all shared objects. A dval is copied only where it leaves the process memory: tasks, codecs, serialization, files, sockets.

## Iterate a dval

Use the value-first loop forms:

:example capy render
function show(values : dval)
{
	for item := values
	{
		print(item, "\n")
	}
	for item, key := values
	{
		print(key, ": ", item, "\n")
	}
}

function RENDER()
{
	show(name: "Ada")
}

:content

`show(name: "Ada")` passes the map `{name: "Ada"}`. Adjacent `key: value` arguments form one map argument. The functions guide describes this form.

The key has type `string`. `dval` list keys are decimal strings. Each item is the object stored in the list, so a change to it is a change to the list's entry. The loop uses a live view and rechecks the current count. A loop that keeps growing its iterable can continue.

## Boundaries

Within a request a `dval` is one object wherever it is passed. Tasks, codecs, and serialization produce copies, since the value leaves the process memory. A `dval` can hold a named function or closure only for the rest of the request. A typed read requires the exact function signature. A mismatch traps. Public BRRB, JSON, YAML, and XML replace callable entries with `none`. Maps keep keys. Lists keep indexes. JSON and YAML preserve `none` as `null`. XML writes `none` as empty text and cannot recover the distinction.

:see-group
guides

:see
type/dvalue
json_decode
yaml_decode
capy-07-collections-and-records
capy-09-web-handlers-and-requests
dval
set
type/bool
type/s32
string
length
