JsonStore Module

From OpenSimulator

Jump to: navigation, search

Contents

Introduction

The JsonStore module enables scripts and region modules to share structured data. Please be aware that it is currently experimental and subject to change which may make older scripts fail to work or work slightly differently.

To use JsonStore when running OpenSimulator on Mono, the version of mono needs to be later than 2.8 (preferably at least in the 2.10 series). On Windows, JsonStore is not yet available in the current stable version 0.7.6.1 - it will only be accessible once OpenSimulator updates the underlying version of .NET that it uses (from v3.5 to v4 which was done in 0.8.0 Dev Master at r/24021).

Nodes are currently restricted to array, object and string. If JSON is added (via JsonCreateStore(), JsonSetJson() or other such methods) that contains raw number values, then these can still be retrieved but only as strings.

Enabling the Module

Add the following to your OpenSim.ini to enable the JsonStore module: 

[JsonStore]
Enabled = true

If you want to access JsonStore via script methods, you will also need to enable permission for MOD functions to run, if you have not done so already:

[XEngine]
AllowMODFunctions = true

General Points

  • The module works by allow scripts to create their own stores for JSON data. JSON can either be written directly, read in from a notecard or added via some other means (such as a separate request to fetch data from an external server).
  • Stores are referenced via a key. This key can be passed to other scripts so that they can share the same store.
  • Stores are not persisted. Once the simulator is reset the data will disappear if other steps aren't taken to store it (such as within notecards).
  • If you have finished with a store, you should remove it with the JsonDestroyStore() function. However, stores created by a script will be garbage collected when the script is deleted or reset. This can affect other scripts if they have access to a shared store.
  • If you want to see the whole contents of a store via script for debugging purposes, then a good way to do this is to print the serialized JsonStore with the script line
llOwnerSay(JsonGetJson(storeID, "."));

JsonStore Path Syntax

A JsonStore path is a '.' separated string of tokens that reference elements of the Json store. Path tokens are either strings (for structure access) or array references. The '+' token refers to the end of the array and is used to append values to an array. Strings are quoted with "{" and "}" characters. Array references are quoted with "[" and "]". When there is no ambiguity, the '.' separator can be dropped.

The function JsonList2Path can be used to convert a list of tokens into a JsonStore path. This is particularly useful if you want to iterate through arrays in the store.

Formal Syntax

Path --> Token | Path '.' Token
Token --> '[' Index ']' | '{' Identifier '}' | SimpleIdentifier
Index --> '+' | [0-9]+
Identifier --> [^}]+
SimpleIdentifier --> [a-zA-Z]+

Examples

// reference the root node, as used in calls such as JsonGetJson(storeId, ".") 
// to serialize the whole store
"."  

// reference an element
"elem" 

// reference an array item
"[0]"  

// reference an array item where elem is the key.  This is a shortcut for elem.[0]
"elem[0]" 

// reference an array item where elem is the key.
"elem.[0]" 

// reference an array item where elem is the key and elem itself 
// contains braces {}, brackets [] or periods .
"{elem}.[0]" 

// this is a two token path with the identifier "foo.bar" and array reference "0"
"{foo.bar}.[0]" 

// this is a two token path with the identifier "foo.bar" and array reference "0" 
// and foo or bar contain elements that need to be delineated.
"{foo}.{bar}.[0]" 

// this is a four token path: [{foo}, {bar}, [0], {goof}]
"foo.bar[0].goof" 

JsonStore Value Syntax

JsonStore Region Module API

Methods for manipulating the region store are made accessible via the IJsonStore interface in the Interfaces directory of OpenSim.Region.Framework.dll. These methods are

public delegate void TakeValueCallback(string s);
 
    public enum JsonStoreNodeType
    {
        Undefined = 0,
        Object = 1,
        Array = 2,
        Value = 3
    }
 
    public enum JsonStoreValueType
    {
        Undefined = 0,
        Boolean = 1,
        Integer = 2,
        Float = 3,
        String = 4,
        UUID = 5
    }
 
    public interface IJsonStoreModule
    {
        bool AttachObjectStore(UUID objectID);
        bool CreateStore(string value, ref UUID result);
        bool DestroyStore(UUID storeID);
 
        JsonStoreNodeType GetNodeType(UUID storeID, string path);
        JsonStoreValueType GetValueType(UUID storeID, string path);
 
        bool TestStore(UUID storeID);
 
        bool SetValue(UUID storeID, string path, string value, bool useJson);
        bool RemoveValue(UUID storeID, string path);
        bool GetValue(UUID storeID, string path, bool useJson, out string value);
 
        void TakeValue(UUID storeID, string path, bool useJson, TakeValueCallback cback);
        void ReadValue(UUID storeID, string path, bool useJson, TakeValueCallback cback);
 
        int GetArrayLength(UUID storeID, string path);
    }

TODO: Documentation on these methods. At this point, you will need to refer to the comparable script functions below.

JsonStore Script Functions

Constants

The JsonStore module provides functions to interrogate the store to test for node and value types. The following constants are returned by the JsonGetNodeType function:

  • JSON_NODETYPE_UNDEF - the node type is unknown or the node does not exist
  • JSON_NODETYPE_OBJECT - the node is a key-accessed dictionary
  • JSON_NODETYPE_ARRAY - the node is an index-accessed array
  • JSON_NODETYPE_VALUE - the node is a value

And the following constants describe the value types of a JSON_NODETYPE_VALUE node:

  • JSON_VALUETYPE_UNDEF - the value is unknown or the node is not a value
  • JSON_VALUETYPE_BOOLEAN - the value of the node is a boolean
  • JSON_VALUETYPE_INTEGER - the node has an integer value
  • JSON_VALUETYPE_FLOAT - the node has a float value
  • JSON_VALUEYTYPE_STRING - the node has a string value

Note that all accessors on a JsonStore currently return a string that can be cast to another value type.

Basic Functions

These functions allow you to manipulate stores, set get and test values and read and write JSON to notecards.

  • JsonCreateStore
  • JsonAttachObjectStore
  • JsonDestroyStore
  • JsonTestStore
  • JsonRezAtRoot
  • JsonReadNotecard
  • JsonWriteNotecard
  • JsonList2Path
  • JsonGetNodeType/JsonGetValueType
  • JsonGetValue/JsonGetJson
  • JsonSetValue/JsonSetJson
  • JsonRemoveValue
  • JsonGetArrayLength
  • JsonTakeValue/JsonTakeJson
  • JsonReadValue/JsonReadJson

key storeID = JsonCreateStore(string jsonvalue)

Create a JsonStore and initialize it using the Json encoded value. The new store identifier is returned.

// Create a JsonStore initialized with a key 'Hello' point towards the string 'World' 
// and a key 'Event' pointing to an array with two values '1, 2'
default
{
    touch_start(integer n)
    {
        key storeID = JsonCreateStore("{'Hello' : 'World', 'Event' : ['1', '2']}");
 
        JsonDestroyStore(storeID);
    }
}

integer status = JsonAttachObjectStore()

Ensure that a store is associated with the object (create the store if it doesn't already exist). The store identifier will be the same as the object identifier in which the script is running.

integer status = JsonDestroyStore(key storeID)

Destroy the JsonStore associated with the provided identifier. You should call this whenever you're finished with a JsonStore.

Returns 1 if the operation is successful. Returns 0 if the operation was not successful or the store does not exist.

default
{
    touch_start(integer n)
    {
        key storeID = JsonCreateStore("{'Hello' : 'World', 'Event' : ['1', '2']}");
        integer res = JsonDestroyStore(storeID);
 
        llOwnerSay("Result " + (string)res);
 
        JsonDestroyStore(storeID);
    }
}

integer status = JsonTestStore(key storeID)

Returns 1 if the identified store exists or 0 if it does not. Used to test if a shared store has been created.

key requestID = JsonWriteNotecard(key storeID, string path, string notecard)

Request that the value identified by the given path be Json encoded and written to the notecard. The function returns the request identifier. When the operation completes, a link_message event is generated with the request identifier.

key requestID;
 
default
{
    touch_start(integer n)
    {
        key storeID = JsonCreateStore("{'Hello' : 'World', 'Event' : ['1', '2']}");
 
        requestID = JsonWriteNotecard(storeID, "", "nc1");
    }
 
    link_message(integer sender, integer ival, string sval, key id)
    {
        if (sender != -1) return;
        if (id == requestID)
        {
            llOwnerSay("Notecard written");
 
            JsonDestroyStore(storeID);
        }
    }
}

key requestID = JsonReadNotecard(key storeID, string path, key assetID)

Request that the Json encoded content of a notecard be decoded and  placed in the structure in the store at the given path. The function returns the request identifier. When the operation completes, a link_message event is generated with the request identifier.

This example presumes that the prim contains the "nc1" notecard saved in the JsonWriteNotecard() example.

key storeID;
key requestID;
 
default
{
    touch_start(integer n)
    {       
        storeID = JsonCreateStore("{}"); 
        requestID = JsonReadNotecard(storeID, "", "nc1");
    }
 
    link_message(integer sender, integer ival, string sval, key id)
    {
        if (sender != -1) return;
 
        if (id == requestID)
        {
            llOwnerSay("notecard read: " + JsonGetValueJson(storeID,"Event[0]"));       
 
            JsonDestroyStore(storeID);
        }
    }
}

key requestID = JsonRezAtRoot(string item, vector pos, vector velocity, rotation rot, string param)

This function is very similar to llRezAtRoot except for two differences. The first is that the string "param" is parsed as JSON and placed into a store identified by the newly created objects identifier. The second is that a link_message event is generated for each of the objects created. The integer parameter is the number of objects remaining to be created (for coalesced objects), the string parameter is the identifier of the newly created object, and the key parameter is the request identifier returned by the call to JsonRezAtRoot.

state create
{
    touch_start(integer i)
    {
        // Rez the object "reztest" from the current object's inventory, give it the json
        // fragment "{ 'start' : 'abc" }" as a start parameter in its object store
        string sparam = "{ 'start' : 'abc' }"
        reqID = JsonRezAtRoot("reztest", llGetPos() + <0.0, 0.0, 5.0>, ZERO_VECTOR, llGetRot(), sparam);
    }
 
    link_message(integer sender, integer result, string objectid, key id)
    {
        if (sender != -1)
            return;
 
        if (id == reqID)
            llOwnerSay("created object " + msg);
    }            
}

And the new object could have use the following code to extract the value from the object store:

default 
{
    // ---------------------------------------------
    state_entry()
    {
        if (JsonTestStore(llGetKey()))
        {
            llOwnerSay("pulling start parameters from object store");
            llOwnerSay("start parameters are " + JsonGetJson(llGetKey(), "."));
        }
     }
 
    // ---------------------------------------------
    on_rez(integer p)
    {
        llResetScript();
    }
}

string value = JsonGetValue(key storeID, string path)

Return the value identified by the path. In the case of JsonGetValue() the value must be a string. If the value exists then it is returned. If the value does not exist or the store does not exist then an empty string is returned.

The raw string is returned. If you want a string token that you could then use with JsonSetValueJson() then you will need to use JsonGetValueJson().

default
{
    touch_start(integer n)
    {
        key storeID = JsonCreateStore("{'Hello' : { 'World' : 'Today' } }");
        string res = JsonGetValue(storeID, "Hello.World");
 
        llOwnerSay("Result " + res); // res will be Today
 
        JsonDestroyStore(storeID);
    }
}

string jsonvalue = JsonGetJson(key storeID, string path)

Return the value identified by the path. In the case of JsonGetJson() the value will be Json encoded string, array or dictionary. If the value does not exist or the store does not exist then an empty string is returned.

Please note that if the value is a string (i.e. a leaf-node), then it will be returned as a token surrounded by single quotes (') instead of as a raw string. This can then be used in JsonSetJson().

default
{
    touch_start(integer n)
    {
        key storeID = JsonCreateStore("{'Hello' : { 'World' : 'Today' } }");
        string res = JsonGetJson(storeID, "Hello");
 
        llOwnerSay("Result " + res);
 
        JsonDestroyStore(storeID);
    }
}

integer status = JsonSetValue(key storeID, string path, string value)

Save the value at the location identified by the path in the store. Any value currently at the location will be replaced. JsonSetValue() assumes that the value is a string.

default
{
    touch_start(integer n)
    {
        key storeID = JsonCreateStore("{'Hello' : 'World', 'Event' : ['1', '2']}");
 
        integer intRes = JsonSetValue(storeID, "Pancake.Banana", "Apple");   
        llOwnerSay("Set result " + (string)intRes);
 
        string stringRes = JsonGetValue(storeID, "Pancake.Banana");
        llOwnerSay("Get result " + stringRes);
 
        JsonDestroyStore(storeID);
    }
}

integer status = JsonSetJson(key storeID, string path, string jsonvalue)

Save the JSON at the location identified by the path in the store. Any data currently at the location will be replaced. The value given must be a JSON encoded string. If you want to specify a string leaf-node, then this must be surrounded by single quotes (').

default
{
    touch_start(integer n)
    {
        key storeID = JsonCreateStore("{'Hello' : 'World'");
 
        integer intRes = JsonSetValue(storeID, "Hello.World", "'Apple'");   
        llOwnerSay("Set result " + (string)intRes);
 
        string stringRes = JsonGetValue(storeID, "Hello.World"); // returns Apple
        llOwnerSay("Get result " + stringRes);
 
        intRes = JsonSetJson(storeID, ".", "{'Event' : ['1', '2']}");   
        llOwnerSay("Set result " + (string)intRes);
 
        string stringRes = JsonGetValue(storeID, "Event[0]"); // returns 1
        llOwnerSay("Get result " + stringRes);
 
        JsonDestroyStore(storeID);
    }
}

integer status = JsonRemoveValue(key storeID, string path)

Remove from the store the value identified by the path.

(In OpenSimulator 0.7.5). Returns 1 if the value was removed or the value did not exist. Returns 0 if the store does not exist.

(In next release). Returns 1 if value was removed. Returns 0 if the path does not exist or the store does not exist.

default
{
    touch_start(integer n)
    {
        key storeID = JsonCreateStore("{'Hello' : 'World', 'Event' : ['1', '2']}");
 
        integer intRes = JsonRemoveValue(storeID, "Hello");   
        llOwnerSay("Remove result " + (string)intRes);
 
        intRes = JsonTestPath(storeID, "Hello");
        llOwnerSay("TestPath result " + (string)intRes);
 
        JsonDestroyStore(storeID);
    }
}

integer res = JsonGetArrayLength(key storeID, string path)

Current development code only (will be released in OpenSimulator 0.7.6).

Get the length of the array at the given path. Returns -1 if

  • The path does not exist
  • The node at the path is not an array
  • The JsonStore for the storeID does not exist.


default
{
    touch_start(integer n)
    {
        key storeID = JsonCreateStore("{'Hello' : { 'World' : ['1', '2'] } }");
 
        integer res= JsonGetArrayLength(storeID, "Hello.World");
        llOwnerSay("Result " + (integer)res);  // should be 2
 
        JsonDestroyStore(storeID);
    }
}

Helper Functions

These are functions to help with manipulating JSON or using the store.

  • JsonList2Path
  • JsonGetNodeType
  • JsonGetValueType


string path = JsonList2Path(list components)

Convert the given list of components to a path. Components can be strings or integers. Strings are used as ordinary path name components. Integers are used as index components to arrays.

This method exists chiefly to make it easier to iterate through arrays. For example

default
{
    touch_start(integer n)
    {
        key storeID = JsonCreateStore("{'Hello' : {'World' : [{'name' : 'pete'}, {'name' : 'tom'}, {'name' : 'wilma'}]}}");
 
        integer i;
        string path;
        string name;
        for (i = 0; i < JsonGetArrayLength(storeID, "Hello.World"); i++)
        {
            path = JsonList2Path(["Hello.World", i, "name"]);           
            name = JsonGetValue(storeID, path);
            llOwnerSay(path + " is '" + name + "'");
        }
 
        JsonDestroyStore(storeID);
    }
}

int nodetype = JsonGetNodeType(key storeid, string path)

Return the type of node identified by the specified path. The following node types could be returned:

  • JSON_NODETYPE_UNDEF - the node type is unknown or the node does not exist
  • JSON_NODETYPE_OBJECT - the node is a key-accessed dictionary
  • JSON_NODETYPE_ARRAY - the node is an index-accessed array
  • JSON_NODETYPE_VALUE - the node is a value

int valuetype = JsonGetValueType(key storeid, string path)

For nodes in the store that specify values, return the type of value stored. Note that currently only string values can be set or returned through the JsonGetValue and JsonSetValue script functions. However, other values can be specified inside Json fragments in notecards or using the JsonSetJson or JsonGetJson functions.

The following value types could be returned:

  • JSON_VALUETYPE_UNDEF - the value is unknown or the node is not a value
  • JSON_VALUETYPE_BOOLEAN - the value of the node is a boolean
  • JSON_VALUETYPE_INTEGER - the node has an integer value
  • JSON_VALUETYPE_FLOAT - the node has a float value
  • JSON_VALUEYTYPE_STRING - the node has a string value

Advanced Functions

These are functions that allow scripts to receive notifications of values when they become available. This is useful for signalling between scripts or between scripts and region modules.

  • JsonTakeValue/JsonTakeValueJson
  • JsonReadValue/JsonReadValueJson

key requestID = JsonTakeValue(key storeID, string path)
key requestID = JsonTakeValueJson(key storeID, string path)

Request that the value identified by the path be removed from the store and returned to the script when it is available. The value will be returned through a link_message event with the requestID. JsonTakeValue() will request a string value. JsonTakeValueJson() will request a Json encoded string that corresponds to a string, array or hash value.

This function is used to wait for a value to be available and then return. Since the operation of read and remove is atomic, it can be used to implement locks, task queues and other synchronization primitives.

default
{
    state_entry()
    {
        key requestID = JsonTakeValue(storeID,"Event.Lock");
    }
 
    link_message(integer sender, integer ival, integer sval, key id)
    {
        if (sender != -1) return;
        if (id == requestID)
        {
            // we now have a "lock"
            llOwnerSay("read: " + sval);
 
            // release the "lock" by placing the value back in the store
            llSetValue(storeID,"Event.Lock",sval);
        }
    }
}

key requestID = JsonReadValue(key storeID, string path)
key requestID = JsonReadValueJson(key storeID, string path)

Request that the value identified by the path be returned to the script when it is available. The value will be returned through a link_message event with the requestID. JsonReadValue() will request a string value. JsonReadValueJson() will request a Json encoded string that corresponds to a string, array or hash value.

Unlike the JsonTakeValue() operation, the JsonReadValue operation does not remove the value from the store once it becomes available.

Examples

Personal tools
About This Wiki