Dynamic Object in GoloScript
Introduction
A Dynamic Object is a flexible data structure that allows you to add properties and methods on the fly, without defining a schema beforehand. It’s GoloScript’s equivalent to JavaScript objects or Python dictionaries with methods, but with an elegant syntax inspired by dynamic languages.
What is a Dynamic Object?
A Dynamic Object consists of two distinct elements:
- Properties: key-value pairs that store data
- Methods: named functions that define behaviors
Unlike classic objects in statically typed languages, you don’t need to define a class or structure in advance. The object can evolve dynamically during execution.
How Does a Dynamic Object Work?
Creation
To create a Dynamic Object, use the DynamicObject() function:
let obj = DynamicObject()
This creates an empty object, with no properties or methods.
Defining Properties
Properties are defined with the syntax : propertyName(value):
let person = DynamicObject()
: name("Alice")
: age(30)
: city("Paris")
Mechanism: when you call : propertyName(value) with an argument, GoloScript:
- Creates or updates the
propertyNameproperty with the valuevalue - Returns the object itself to enable chaining (fluent API)
Accessing Properties
To read a property, use the same syntax but without an argument:
println(person: name()) # Prints: Alice
println(person: age()) # Prints: 30
Mechanism: when you call : propertyName() without an argument, GoloScript:
- First checks if a method named
propertyNameexists - Otherwise, returns the value of the
propertyNameproperty
Defining Methods
Methods are defined by assigning a lambda (anonymous function):
let person = DynamicObject()
: name("Alice")
: greet(|this| {
println("Hello, my name is", this: name())
})
Key points:
- The first parameter of the lambda is always
this, which references the dynamic object itself - This allows the method to access the object’s properties
- You can define additional parameters after
this
Calling Methods
To call a method, use the syntax : methodName() or : methodName(args...):
person: greet() # Prints: Hello, my name is Alice
With arguments:
let calculator = DynamicObject()
: add(|this, a, b| {
return a + b
})
let result = calculator: add(5, 3) # result = 8
Method Chaining
One of the most powerful features of Dynamic Objects is method chaining:
let person = DynamicObject()
: name("Alice")
: age(30)
: city("Paris")
: greet(|this| {
println("I'm", this: name(), "from", this: city())
})
person: greet()
Your methods can also return this to enable chaining:
let counter = DynamicObject()
: value(0)
: increment(|this| {
this: value(this: value() + 1)
return this
})
: decrement(|this| {
this: value(this: value() - 1)
return this
})
counter: increment(): increment(): increment() # value = 3
println(counter: value()) # Prints: 3
Introspection
GoloScript provides special methods to examine a Dynamic Object:
properties()
Returns an array containing the names of all properties:
let obj = DynamicObject()
: id(1)
: name("test")
println(obj: properties()) # Prints: [id, name]
methods()
Returns an array containing the names of all methods:
let obj = DynamicObject()
: validate(|this| { return true })
: save(|this| { println("Saving...") })
println(obj: methods()) # Prints: [validate, save]
⚠️ Note: The order of returned elements is not guaranteed (limitation of the underlying Go implementation).
define() Method
To explicitly define a property or method, you can use define(key, value):
let obj = DynamicObject()
obj: define("property", "value")
obj: define("method", |this| { println("Hello!") })
This method is useful when you want to define a key dynamically or avoid ambiguity.
JSON Serialization
Dynamic Objects can be serialized to JSON with the toJSON() function:
let person = DynamicObject()
: name("Alice")
: age(30)
: greet(|this| { println("Hello") })
println(toJSON(person)) # {"name":"Alice","age":30}
Important: only properties are serialized. Methods are ignored during JSON conversion.
Use Cases
Dynamic Objects are particularly useful for:
- Dynamic configuration: create configuration objects that evolve
- Rapid prototyping: test ideas without defining rigid structures
- Data mapping: transform JSON data into manipulable objects
- Builder pattern: build complex objects with a fluent API
- Scripting: write flexible scripts that adapt to needs
Complete Example
# Create a person with properties and methods
let person = DynamicObject()
: firstName("Alice")
: lastName("Smith")
: birthYear(1990)
: fullName(|this| {
return this: firstName() + " " + this: lastName()
})
: age(|this| {
return 2024 - this: birthYear()
})
: greet(|this| {
println("Hello! I'm", this: fullName())
println("I'm", this: age(), "years old")
})
# Use the object
person: greet()
# Prints:
# Hello! I'm Alice Smith
# I'm 34 years old
# Introspection
println("Available properties:", person: properties())
println("Available methods:", person: methods())
# Serialization
println("JSON:", toJSON(person))
# {"firstName":"Alice","lastName":"Smith","birthYear":1990}