I'm trying to write a template to set a value to an object's field, at run time, where the field name and the value are passed to the template as strings. The code below illustrates what I'm trying to do. Is there a way to accomplish this? i.e. is there some equivalent of my hypothetical untypedFromString function below?
type Foo = object
bar: int
template setObjectField(obj: untyped, field: string, value: string): untyped =
let
f = untypedFromString(field)
v = untypedFromString(value)
obj.f = v
a = Foo(bar: 1)
setObjectField(a, "bar", "3")
Nim is a compiled language, this means when you access a field the regular way the constant relative memory location of an object field has to be known at compile time, which in turn forbids these things.
While it is possible to associate this offset with strings, either yourself (offsetof is the key here). Or you can use the runtime time informations which are meant for internal use via typeinfo <https://nim-lang.org/docs/typeinfo.html>. Neither of these methods are recommended in any way, unless you are very very sure what you want to do.
You're probably better off using either a simple table <https://nim-lang.org/docs/tables.html> or json nodes <https://nim-lang.org/docs/json.html> (they can be created manually). Though again, think triece, you loose most of the speed and type safety when using the latter, essentially you then access variables like in a interpreted language.
One possible implimentation with fieldPairs:
import std/strutils
from std/macros import expandMacros
type Foo = object
ival: int
fval: float
proc setField[T: int](val: var T, str: string) =
val = parseInt(str)
proc setField[T: float](val: var T, str: string) =
val = parseFloat(str)
template implSetObjectField(obj: object, field, val: string): untyped =
block fieldFound:
for objField, objVal in fieldPairs(obj):
if objField == field:
setField(objVal, val)
break fieldFound
raise newException(ValueError, "unexpected field: " & field)
proc setObjectField[T: object](obj: var T, field, val: string) =
# inside a generic proc to avoid side-effects and reduce code size.
expandMacros: # to see what it generates
implSetObjectField(obj, field, val)
var a = Foo(ival: 1, fval: 2.0)
setObjectField(a, "ival", "38")
setObjectField(a, "fval", "4e8")
echo a
Approximate fields setter and getter with JsonNode and some experimental dooperator magic (from https://forum.nim-lang.org/t/5000#31345 and https://github.com/status-im/nim-cookbook/blob/master/dynamic_approximating_dynamic_types.nim)
import json
{.experimental: "dotOperators".}
type
Action = ref object
properties: JsonNode
template `.`(action: Action, field: untyped): untyped =
action.properties[astToStr(field)]
template `.=`(action: Action, field, value: untyped): untyped =
action.properties[astToStr(field)] = %value
# Our main object, the fields are dynamic
var a = Action(
properties: %*{
"layer": 0,
"add": true,
"vis": false,
"new_name": "fancy_name"
}
)
# And usage, those are not real fields but there is no difference in syntax
echo a.new_name # "fancy_name"
a.algo = 10
echo a.algo # 10