Yes Second Life

セカンドライフに早9年、現役セカンドライファーの考えてること

LSLスクリプトでJSONを扱う(単要素処理編)

これは、セカンドライフ 技術系 Advent Calendar 2013向けに書かれた記事です。LSLにJSONを扱う関数が追加されたので、今回はそれを紹介します。

JSONとは

JSONはJavascriptの記法で書かれたデータなのですが、いろんなプログラミング言語にJSONを扱うためのライブラリがあるので、さまざまなプログラム間でデータをやりとりするときに使われています。

こんなかんじでオブジェクトと配列の組み合わせでJSONは構成されます。

// JSONオブジェクト
{ "a" : 1, "b" : true } 

// JSON配列
[ "c", 3, false ]

// 組み合わせ
[ "c", 3, false, { "a" : 1, "b" : true } ] 

LSLには「値を1つずつ処理する方法」「JSONをリストに一括で変換する方法」の、2通りのやり方が用意されています。一気に紹介しようと思ったのですが、意外と長くなってしまったので、今回は値を1つずつ処理する方法を紹介します。

llJsonGetValue

string llJsonGetValue(string json, list specifiers)

JSONから指定したパスにある値を文字列として取得します。第一引数に対象のJSONを指定して、第二引数のListでパスを指定します。

// JSONをリテラルで書く場合、ダブルクォーテーションはエスケープが必要です
string value1 = llJsonGetValue("{ \"a\" : 1, \"b\" : true }", ["a"]);

// 1と表示されます
llOwnerSay(value1);

// ネストした例、配列の4番目(0から始まるので)のオブジェクトの、bをKeyにもつ値を取得
string value2 = llJsonGetValue("[ \"c\", 3, false, { \"a\" : 1, \"b\" : true } ]", [3, "b"]);

// TRUEと表示してほしいところですが、﷖と表示されます
llOwnerSay(value2);

2つめの例をみるとわかるように、リストにパスとなる値を書いていくことで深い位置にある値もとってくることができます。しかし、うまくTRUEと表示されてくれません。LSLには定数として「TRUE」が用意されてるんですが、これって単に数値の1の置き換えでしかないので、実は真の意味で真偽値を表す定数ってないんですね。そこで、JSONの真偽値を表すために「JSON_TRUE」「JSON_FALSE」が追加されています。

// trueを取得します
string value1 = llJsonGetValue("{ \"a\" : 1, \"b\" : true }", ["b"]);

// このように、条件分岐を使うと人にもわかるように表示できます
if(value1 == JSON_TRUE)
{
    llOwnerSay("TRUEを取得しました");
}

定数は他にもあります。

// nullを取得します
string value1 = llJsonGetValue("{ \"a\" : 1, \"b\" : null }", ["b"]);

// nullは、JSON_NULLであらわします
if(value1 == JSON_NULL)
{
    llOwnerSay("NULLを取得しました");
}

// 存在しないKeyを指定します
string value2 = llJsonGetValue("{ \"a\" : 1, \"b\" : null }", ["c"]);

// JSON_INVALIDが返ります
if(value2 == JSON_INVALID)
{
    llOwnerSay("不正な操作です");
}

llJsonSetValue

string llJsonSetValue(string json, list specifiers, string value)

当然値をセットする関数もあります。第一引数に対象のJSONを指定して、第二引数のListでパスを指定、第三引数でセットする値を指定します。返り値は値が更新されたJSONです。

// aをKeyとする値に数値の2をセットしています
string value1 = llJsonSetValue("{ \"a\" : 1, \"b\" : true }", ["a"], "2");

// {"a":2,"b":true}と表示されます
llOwnerSay(value1);

// ネストした例、今度は文字列の2をセットしています
string value2 = llJsonSetValue("[ \"c\", 3, false, { \"a\" : 1, \"b\" : true } ]", [3, "a"], "\"2\"");

// ["c",3,false,{"a":"2","b":true}]と表示されます。「数値」と「数字の文字列」は区別されています
llOwnerSay(value2);

// aをKeyとする値にオブジェクトをセットしています
string value3 = llJsonSetValue("{ \"a\" : 1, \"b\" : true }", ["a"], "{\"c\":1,\"d\":2}");

// {"a":{"c":1,"d":2},"b":true}と表示されます
llOwnerSay(value3);

// 存在しないKeyを指定すると、キーと値のペアを追加できます
string value4 = llJsonSetValue("{ \"a\" : 1, \"b\" : true }", ["c"], "3");

// {"a":1,"b":true,"c":3}と表示されます
llOwnerSay(value4);

数値と数字の文字列が区別されたり、オブジェクトをセットできたり、けっこう自由度が高いです。また特殊な定数を使った便利な操作も用意されています。

// JSON_NULLを使って、nullをセットしています
string value1 = llJsonSetValue("{ \"a\" : 1, \"b\" : true }", ["a"], JSON_NULL);

// {"a":null,"b":true}と表示されます
llOwnerSay(value1);

// JSON_APPEND定数を使って、配列の最後に値を追加しています。要素数がわからないとき便利です
string value2 = llJsonSetValue("[ \"c\", 3, false, { \"a\" : 1, \"b\" : true } ]", [JSON_APPEND], "5");

// ["c",3,false,{ "a" : 1, "b" : true },5]と表示されます
llOwnerSay(value2);

// ネストした配列でも可能です
string value3 = llJsonSetValue("[ \"c\", 3, false, [ \"a\", \"b\"] ]", [3, JSON_APPEND], "6");

// ["c",3,false,["a","b",6]]と表示されます
llOwnerSay(value3);

// JSON_DELETE定数を使って、値を削除しています
string value4 = llJsonSetValue("{ \"a\" : 1, \"b\" : true }", ["a"], JSON_DELETE);

// {"b":true}と表示されます
llOwnerSay(value4);

// 配列でも削除できます
string value5 = llJsonSetValue("[ \"a\", \"b\"}", [0], JSON_DELETE);

// ["b"]と表示されます
llOwnerSay(value5);

llJsonValueType

string llJsonValueType(string json, list specifiers)

指定したパスにある値のタイプを取得します。第一引数に対象のJSONを指定して、第二引数のListでパスを指定します。返り値は、以下の定数のどれかです。

  • JSON_INVALID
  • JSON_OBJECT
  • JSON_ARRAY
  • JSON_NUMBER
  • JSON_STRING
  • JSON_NULL
  • JSON_TRUE
  • JSON_FALSE
string json = "[ \"c\", 3, false, { \"a\" : null, \"b\" : true } ]";

// JSON_STRING
string type1 = llJsonValueType(json, [0]);
if(type1 == JSON_STRING) llOwnerSay("string");

// JSON_NUMBER
string type2 = llJsonValueType(json, [1]);
if(type2 == JSON_NUMBER) llOwnerSay("number");

// JSON_TRUE
string type3 = llJsonValueType(json, [3, "b"]);
if(type3 == JSON_TRUE) llOwnerSay("true");

// JSON_NULL
string type4 = llJsonValueType(json, [3, "a"]);
if(type4 == JSON_NULL) llOwnerSay("null");

// JSON_OBJECT
string type5 = llJsonValueType(json, [3]);
if(type5 == JSON_OBJECT) llOwnerSay("object");

// JSON_INVALID
string type6 = llJsonValueType(json, [9]);
if(type6 == JSON_INVALID) llOwnerSay("invalid");

以上で、単要素処理編はおしまいです。しかしJSONのパスをリストで表すというのが、なんか斬新でした。次回、一括変換編は万一アドベントカレンダーが完走しなかった場合に書こうとおもいます。ま、まあきっと完走するよね・・・。