Skip to content

$GOBJ

References:

$GOBJ = {} syntax (associative, with names/keys)

An associative object that stores key-value pairs. Elements are accessed by their names (keys) rather than by position. This follows Grapa Object Notation (GOBJ) terminology for key-value collections.

$GOBJ vs $LIST Comparison

Feature $GOBJ ({}) $LIST ([])
Syntax {'a':1, 'b':2, 'c':3} [1, 2, 3]
Access gobj."a" or gobj["a"] array[0] or array['key']
Type Associative Positional (with key search)
Keys Named keys Numeric indices + key search
Order Key-based Position-based
2D Access .getfield(index, field) .getfield(index, field)

Note: This differs from some other languages where [] is called a "list" and {} is called a "dictionary" or "object". Grapa follows traditional C terminology.

String Labels vs ID Labels in $GOBJ

Important: In $GOBJ objects, there's a crucial distinction between string labels and ID labels:

  • String labels ("x") - $STR values used as labels for data items
  • ID labels (x) - $ID values used as labels for variables/functions
  • Variable references (@x) resolve to the current value of variable x

Both $ID and $STR can be used as labels, but they serve different purposes and are accessed differently.

This separation prevents data items from overriding class methods:

/* Example showing the distinction */
x = "hi";
z = "by";

/* Create object with both string and ID labels */
d = {"x":"x str", x:"x id", @x:"test", @"z":"test2"};

/* Access string-labeled data item */
d."x"                    /* Returns: "x str" */

/* Access ID-labeled item */
d.x                      /* Returns: "x id" */

/* Access variable-referenced items */
d."hi"                   /* Returns: "test" (from @x where x="hi") */
d."by"                   /* Returns: "test2" (from @"z" where z="by") */

/* The object structure */
d                        /* Returns: {"x":"x str",x:"x id","hi":"test","by":"test2"} */

Why This Matters

Without this separation, a data item with label "len" would override the class method .len():

/* BAD: This would break .len() method */
bad_obj = {len:"some data"};
bad_obj.len();           /* Error: tries to access data instead of method */

/* GOOD: Use string labels for data items */
good_obj = {"len":"some data"};
good_obj.len();          /* Works: calls the .len() method */
good_obj."len";          /* Works: accesses the data item */

Best Practices

  • For data items: Use string labels {"key":"value"}
  • For variables/functions: Use ID labels {key:"value"}
  • For dynamic references: Use variable references {@var:"value"}

⚠️ IMPORTANT: Data Item Labels Must Be Quoted

In Grapa Objects ($GOBJ), there's a crucial distinction between data access and method access:

  • Unquoted labels (gobj.key): Searches for $ID or $OP in the object's class hierarchy (methods, properties)
  • Quoted labels (gobj."key"): Accesses data items stored in the object
/* Example: Cryptographic key object */
crypto_keys = {
    'pub': "public_key_data",     /* Data item - must be quoted */
    'priv': "private_key_data",   /* Data item - must be quoted */
    'method': 'rsa'              /* Data item - must be quoted */
};

/* Correct data access */
pub_key = crypto_keys.'pub';      /* ✅ Returns "public_key_data" */
priv_key = crypto_keys.'priv';    /* ✅ Returns "private_key_data" */

/* Incorrect - would search for $ID/$OP named 'pub' */
/* pub_key = crypto_keys.pub;    ❌ Would look for method/property 'pub' */

When to Use $GOBJ vs Other Data Types

Use {} (Grapa Object) When:

  • Small to medium datasets (< 100-500 items typically)
  • Frequent modifications (insertions, deletions, updates)
  • In-memory operations only
  • Simple key-value storage without complex queries
  • Configuration data, user preferences, cache data

Use {}.table() (In-Memory BTree) When:

  • Large datasets (> 100-500 items) where memory efficiency matters
  • Range queries or ordered data access needed
  • Complex queries across multiple fields
  • Memory efficiency at scale is important
  • Still in-memory but need better performance for large datasets

Use $file() (Persistent Storage) When:

  • Data persistence is required (survives program restarts)
  • Very large datasets that don't fit in memory
  • Disk-based storage with BTree indexing
  • Long-term data storage and retrieval
  • File system integration needed

Performance Characteristics:

  • {} (Linked List): Very fast for small datasets due to optimized double-linked list implementation
  • {}.table() (BTree): Slower than {} for small datasets but more memory-efficient for large datasets
  • $file() (Persistent): Disk I/O overhead but provides persistence and handles unlimited data size

Key Insight: Don't add indexing to {} - the overhead would likely make it slower, not faster. The double-linked list implementation is already highly optimized.

Action Example Result
Create {'a':1, 'b':2, 'c':3} {"a":1,"b":2,"c":3}
Access {'a':1, 'b':2, 'c':3}."a"
{'a':1, 'b':2, 'c':3}[1]
{'a':1, 'b':2, 'c':3}[-1]
1
1
3
Assign x = {'a':1, 'b':2, 'c':3};
x."b" = "x";
x["b"] = "by";
x[-2] = 1234;


{"a":1,"b":"x","c":3}
{"a":1,"b":"by","c":3}
{"a":1,"b":1234,"c":3}
Append x = {'a':1, 'b':2};
x += ('c':3);
x;


{"a":1, "b":2, "c":3}
Append x = {'a':1, 'b':2};
x ++= {'c':3,'d':4};
x;


{"a":1, "b":2, "c":3, "d":4}
Insert x = {'a':1, 'b':2};
x += ('c':3) x[0];
x;


{"c":3,"a":1,"b":2}
Count {'a':1, 'b':2, 'c':3}.len() 3
Remove x = {'a':1, 'b':2, 'c':3};
x -= x[1];
x;


{"a":1, "c":3}

Advanced List Operations

Element Access Methods

Lists support both bracket notation and method-based access:

list = {'a':1, 'b':2, 'c':3};

/* Bracket notation (direct access) */
value = list["a"];        /* 1 */
value = list."a";         /* 1 - quoted for data access */
value = list[0];          /* 1 (by index) */
value = list[-1];         /* 3 (by negative index) */

/* Method-based access */
value = list.get("a");    /* 1 */
value = list.get(0);      /* 1 (by index) */
value = list.get(-1);     /* 3 (by negative index) */

/* Field access methods */
value = list.getfield("a"); /* 1 */
value = list.getfield(0);   /* 1 (by index) */
value = list.getfield(-1);  /* 3 (by negative index) */

2D Access with .getfield()

For 2D access (when accessing nested objects or specific fields), you can use .getfield() with two parameters:

/* Example: File listing object */
d = $file().ls();
/* Returns: [{"$PATH":"","$KEY":"build.py","$TYPE":"FILE","$BYTES":38707}, ...] */

/* Access specific field of a specific item */
test_bytes = d.getfield(9, "$BYTES");        /* 608 (bytes of "test" item) */
test_type = d.getfield(9, "$TYPE");          /* "DIR" (type of "test" item) */
test_key = d.getfield(9, "$KEY");            /* "test" (key of "test" item) */

/* Alternative: Chain access methods */
test_item = d.getfield(9);                   /* {"$PATH":"","$KEY":"test","$TYPE":"DIR","$BYTES":608} */
test_bytes = test_item.getfield("$BYTES");   /* 608 */
test_type = test_item.getfield("$TYPE");     /* "DIR" */

2D Access Methods: - .getfield(index, field): Access specific field of item at index - .getfield(index).getfield(field): Chain access (same result) - .getfield(index)["field"]: Alternative bracket notation

Method Selection Guidelines: - .get(): Use for 1D access by key or index - .getfield(): Use for 1D access by key/index (equivalent to .get()) or 2D access with two parameters - Bracket notation: Use for direct access when you know the structure - Chained methods: Use .getfield().getfield() for 2D access - Quoted access: Use gobj."key" for data items, gobj.key for methods/properties

Assignment Operations (=)

/* Direct property assignment */
list = {'a':1, 'b':2, 'c':3};
list."b" = "x";           /* {"a":1,"b":"x","c":3} - quoted for data access */
list["b"] = "by";         /* {"a":1,"b":"by","c":3} */

/* Assignment by index */
list[1] = 55;             /* {"a":1,"b":55,"c":3} */

/* Assignment by negative index */
list[-2] = 1234;          /* {"a":1,"b":1234,"c":3} */

/* Method-based assignment */
list.set("b", "x");       /* {"a":1,"b":"x","c":3} */
list.set(1, 55);          /* {"a":1,"b":55,"c":3} */
list.set(-2, 1234);       /* {"a":1,"b":1234,"c":3} */

/* Field assignment methods */
list.setfield("b", "x");  /* {"a":1,"b":"x","c":3} */
list.setfield(1, 55);     /* {"a":1,"b":55,"c":3} */
list.setfield(-2, 1234);  /* {"a":1,"b":1234,"c":3} */

/* Compound assignment on accessed elements */
list."b" += "dee";        /* {"a":1,"b":"bydee","c":3} - quoted for data access */
list[0] += 8;             /* {"a":9,"b":"bydee","c":3} */

2D Assignment with .setfield()

For 2D assignment (when modifying nested objects or specific fields), you can use .setfield() with two parameters:

/* Example: File listing object */
d = $file().ls();
/* Returns: [{"$PATH":"","$KEY":"build.py","$TYPE":"FILE","$BYTES":38707}, ...] */

/* Modify specific field of a specific item */
d.setfield(9, "$BYTES", 1024);              /* Change bytes of "test" item to 1024 */
d.setfield(9, "$TYPE", "FILE");             /* Change type of "test" item to "FILE" */

/* Alternative: Chain assignment methods */
test_item = d.getfield(9);                  /* Get the item */
test_item.setfield("$BYTES", 2048);         /* Modify bytes field */
test_item.setfield("$TYPE", "DIR");         /* Modify type field */

2D Assignment Methods: - .setfield(index, field, value): Set specific field of item at index - .getfield(index).setfield(field, value): Chain access and assignment - .getfield(index)["field"] = value: Alternative bracket notation

Addition Operations (+=)

/* Add single key-value pair */
list = {a:1, b:2};
list += (c:3);           /* {a:1, b:2, c:3} */

/* Add multiple key-value pairs */
list += {d:4, e:5};      /* {a:1, b:2, c:3, d:4, e:5} */

/* Insert at specific position */
list += (f:6) list[0];   /* {f:6, a:1, b:2, c:3, d:4, e:5} */

Concatenation Operations (++=)

/* Concatenate two lists */
list1 = {a:1, b:2};
list2 = {c:3, d:4};
list1 ++= list2;         /* {"a":1,"b":2,"c":3,"d":4} */

Removal Operations (-=)

list = {a:1, b:2, c:3, d:4};

/* Remove by key reference */
list -= list.c;          /* {"a":1,"b":2,"d":4} */

/* Remove by key string */
list -= list["b"];       /* {"a":1,"d":4} */

/* Remove by positive index */
list -= list[0];         /* {"d":4} */

/* Remove by negative index */
list -= list[-1];        /* {} */

Unsupported Operations

list = {'a':1, 'b':2, 'c':3};

/* These do NOT work: */
list -= 2;               /* No effect - value-based removal not supported */
list -= "b";             /* No effect - direct string removal not supported */
list -= list.b;          /* Error - trying to remove value, not key */

Concatenation and Extension

list = {'a':1, 'b':2, 'c':3};

/* Add elements */
list += {d:4};                            /* {a:1, b:2, c:3, d:4} */

/* Extend with multiple elements */
list ++= {e:5, f:6};                      /* {a:1, b:2, c:3, d:4, e:5, f:6} */

/* Insert at specific position */
list ++= {g:7}, 2;                        /* Insert at position 2 */

/* Remove elements */
list -= {b:2};                            /* Remove element with key "b" */

List Slicing

Lists support slicing operations for extracting portions of key-value pairs:

Left Slicing (.left())

list = {a:1, b:2, c:3, d:4, e:5};

list.left(3);                              /* {a:1, b:2, c:3} */
list.left(-2);                             /* {a:1, b:2, c:3} */

Right Slicing (.right())

list = {a:1, b:2, c:3, d:4, e:5};

list.right(3);                             /* {c:3, d:4, e:5} */
list.right(-2);                            /* {d:4, e:5} */

Middle Slicing (.mid())

list = {a:1, b:2, c:3, d:4, e:5};

list.mid(1, 3);                            /* {b:2, c:3, d:4} */
list.mid(-3, 2);                           /* {c:3, d:4} */

Note: Slicing methods work with any list size. For empty lists, slicing returns an empty list. Negative indices count from the end.

List Rotation

Lists support left and right rotation operations for reordering key-value pairs:

Left Rotation (.lrot())

list = {a:1, b:2, c:3, d:4};

list.lrot();                              /* {b:2, c:3, d:4, a:1} */
list.lrot(2);                             /* {c:3, d:4, a:1, b:2} */
list.lrot(0);                             /* {a:1, b:2, c:3, d:4} (no change) */

Right Rotation (.rrot())

list = {a:1, b:2, c:3, d:4};

list.rrot();                              /* {d:4, a:1, b:2, c:3} */
list.rrot(2);                             /* {c:3, d:4, a:1, b:2} */
list.rrot(0);                             /* {a:1, b:2, c:3, d:4} (no change) */

Note: Rotation methods preserve all key-value pairs while reordering them. For empty lists or single-element lists, rotation has no effect.

Advanced Element Finding (.findall())

The .findall() method provides enterprise-grade querying capabilities for LIST structures with support for complex patterns and logical operations.

Basic Queries

data = {name:"Alice", age:30, city:"New York"};

/* Find by property existence */
data.findall({has:{name:"age"}})          /* Returns: {data:{name:"Alice",age:30}} */

/* Find by property value */
data.findall({has:{name:"name", value:"Alice"}}) /* Returns: {data:{name:"Alice",age:30}} */

/* Find by value only */
data.findall({has:{value:"Alice"}})       /* Returns: {data:{name:"Alice",age:30}} */

Complex Nested Queries

nested = {user:{name:"Alice", age:30}, admin:{name:"Bob", age:25}};

/* Find nested objects */
nested.findall({has:{name:"user"}})       /* Returns: {"nested":{"user":{"name":"Alice","age":30},"admin":{"name":"Bob","age":25}}} */

/* Find with nested criteria */
nested.findall({has:{name:"user", has:{name:"name", value:"Alice"}}}) /* Returns: {"nested":{"user":{"name":"Alice","age":30},"admin":{"name":"Bob","age":25}}} */

/* Find with multiple nested criteria */
nested.findall({has:{name:"user", has:{name:"age", value:30}}}) /* Returns: {"nested":{"user":{"name":"Alice","age":30},"admin":{"name":"Bob","age":25}}} */

Logical Operations

data = {user:{name:"Alice", role:"admin"}, guest:{name:"Bob", role:"user"}};

/* AND logic */
data.findall({and:[{has:{name:"user"}}, {has:{name:"user", has:{name:"role", value:"admin"}}}]}) /* Returns: {"data":{"user":{"name":"Alice","role":"admin"},"guest":{"name":"Bob","role":"user"}}} */

/* OR logic */
data.findall({or:[{has:{name:"user"}}, {has:{name:"guest"}}]}) /* Returns: {"data":{"user":{"name":"Alice","role":"admin"},"guest":{"name":"Bob","role":"user"}}} */

/* NAND logic */
data.findall({nand:[{has:{name:"user"}}, {has:{name:"user", has:{name:"role", value:"user"}}}]}) /* Returns: {"data":{"user":{"name":"Alice","role":"admin"},"guest":{"name":"Bob","role":"user"}}} */

Working with Results

data = {user:{name:"Alice", age:30}, admin:{name:"Bob", age:25}};

/* Get all results */
results = data.findall({has:{name:"user"}});
results.len()                              /* Returns: 1 */

/* Access individual results */
first = results[0];                        /* Returns: {"user":{"name":"Alice","age":30}} */

/* Access nested properties of results */
first.user.name                            /* Returns: "Alice" */
first.user.age                             /* Returns: 30 */

Error Handling and Boundary Conditions

List Access Behavior

Lists have flexible boundary behavior similar to arrays:

list = {'a':1, 'b':2, 'c':3};

/* Valid access - returns elements */
list.get("a");             /* 1 */
list.get(0);               /* 1 */
list.get(-1);              /* 3 */

/* Boundary behavior - APPENDS instead of error */
list.get("d");             /* Returns null (key doesn't exist) */
list.set("d", 99);         /* APPENDS: {a:1, b:2, c:3, d:99} */
list.set("f", 88);         /* APPENDS: {a:1, b:2, c:3, d:99, f:88} */

/* Method-based access follows same rules */
list.getfield("d");        /* Returns null (key doesn't exist) */
list.setfield("d", 77);    /* APPENDS: {a:1, b:2, c:3, d:99, f:88, d:77} */

/* IMPORTANT: .set()/.setfield() only append at the end */
/* For insertion at specific positions, use += and ++= operators */
list = {'a':1, 'b':2, 'c':3};
list += (d:99) list[1];    /* INSERT at position 1: {a:1, d:99, b:2, c:3} */
list ++= {e:88, f:77} list[0]; /* INSERT multiple at position 0: {e:88, f:77, a:1, d:99, b:2, c:3} */

Key Differences from Vectors

Behavior $GOBJ $VECTOR
Out-of-bounds Get Returns null Returns $ERR
Out-of-bounds Set APPENDS element Returns $ERR
Boundary Policy Flexible (grows) Strict (fixed size)
Use Case Dynamic key-value storage Mathematical structures

Append vs. Error Behavior

list = {'a':1, 'b':2, 'c':3};

/* These APPEND (lists grow dynamically) */
list.set("d", 99);         /* {a:1, b:2, c:3, d:99} */
list.set("f", 88);         /* {a:1, b:2, c:3, d:99, f:88} */
list.setfield("g", 77);    /* {a:1, b:2, c:3, d:99, f:88, g:77} */

/* These return null (key doesn't exist) */
result = list.get("z");    /* null */
result = list.getfield("z"); /* null */

/* Check for missing keys */
if (list.get("z") == null) {
    /* Handle missing key */
}

Insertion vs. Appending

Important Distinction: - .set()/.setfield(): Only append at the end - += and ++= operators: Insert at specific positions

list = {'a':1, 'b':2, 'c':3};

/* .set() only appends at the end */
list.set("d", 99);         /* {a:1, b:2, c:3, d:99} - appends at end */

/* += and ++= insert at specific positions */
list = {'a':1, 'b':2, 'c':3};
list += (d:99) list[1];    /* {a:1, d:99, b:2, c:3} - inserts at position 1 */
list ++= {e:88, f:77} list[0]; /* {e:88, f:77, a:1, d:99, b:2, c:3} - inserts at position 0 */

Error Handling Patterns

list = {'a':1, 'b':2, 'c':3};

/* Safe access with null checking */
result = list.get("missing_key");
if (result == null) {
    result = "Key not found";
}

/* Safe setting (always works - appends if needed) */
list.set("new_key", 99);   /* Always succeeds, appends if needed */

/* Check if key exists before access */
if (list.get("key") != null) {
    value = list.get("key");
} else {
    value = "Key doesn't exist";
}

Comparison with Other Data Types

/* List behavior (flexible) */
list = {'a':1, 'b':2, 'c':3};
list.set("d", 99);         /* APPENDS: {a:1, b:2, c:3, d:99} */

/* Array behavior (flexible) */
arr = [1, 2, 3];
arr.set(3, 99);            /* APPENDS: [1, 2, 3, 99] */

/* Vector behavior (strict) */
vec = [1, 2, 3].vector();
result = vec.set(3, 99);   /* Returns $ERR - strict bounds */

Best Practices

  1. Use lists for dynamic key-value storage where you need flexible sizing
  2. Use vectors for mathematical operations where fixed dimensions matter
  3. Check for null returns when accessing potentially missing keys
  4. Use += and ++= for insertion at specific positions
  5. Use .set()/.setfield() for appending at the end
  6. Use key existence checks before accessing if you need strict behavior