Yes Second Life

セカンドライフ向けWebサービスを作ってました。このブログではVR・メタバースのことを書いていきます

LSLスクリプトでJSONを扱う(一括変換編)

この記事は、セカンドライフ 技術系 Advent Calendar 2013向けに書かれた記事です。今回は、JSONとリストを一括変換するLSL関数を紹介します。

llJson2List

list llJson2List(string src)

JSON文字列を、LSLのリスト変数に変換します。引数に対象のJSONを指定し、返り値が変換したリストになります。

// リストの表示をわかりやすくするため、文字列への変換関数を用意しておきます
string dump(list l)
{
    return "[ " + llDumpList2String(l, ",") + " ]";
}


// JSONオブジェクトを変換してみます
list value1 = llJson2List("{ \"a\" : 1, \"b\" : true }");

// [ a,1,b,﷖ ]と表示されます。strided list(ひとつ飛ばしのリスト)になります
// リストの最後の項は、JSON_TRUEです
llOwnerSay(dump(value1));

// JSON配列を変換してみます
list value2 = llJson2List("[ 1, \"two\", \"3\" ]");

// [ 1,two,3 ]と表示されます。普通にリストに変換されます
// ちなみにリストの要素は全て文字列型になります
llOwnerSay(dump(value2));

// 単一の値を変換してみます
list value3 = llJson2List("1");

// [ 1 ]と表示されます。ひとつだけ要素を含んだリストになります
llOwnerSay(dump(value3));

// keyをnullにした不正なJSONを変換してみます
list value4 = llJson2List("{ \"a\" : 1, null : true }");

// [ ﷐ ]と表示されます。JSON_INVALIDだけ含んだリストです
llOwnerSay(dump(value4));

JSONオブジェクトは、strided list(ひとつ飛ばしのリスト)に変換されます。LSLにはstrided listを扱える関数がいくつかあるので活用できそうです。また、JSONをリストに変換した時、リストの要素は全て文字列型になります。これは例えば数値をfloatに勝手に変換して桁落ちしたりしないような配慮だとおもいます。

不正なJSONが入力された場合は、JSON_INVALIDのみ含んだリストが返されるので、入力値のJSONが信頼できない場合は、条件分岐でJSON_INVALIDと比較するのが良さそうです。

if(llList2String(value4, 0) != JSON_INVALID)
{
    // 本来の処理
    // ...
}

では、ネストしたJSONは正しく変換できるのでしょうか。

string json = "
{
    \"a\" : 1,
    \"b\" : [ 1, \"two\", \"3\" ],
    \"c\" : { \"d\" : 2, \"e\" : false }
}";

// ネストしたJSONを変換してみます
list value1 = llJson2List(json);

// [ a,1,b,[ 1, "two", "3" ],c,{ "d" : 2, "e" : false } ]と表示されます。
llOwnerSay(dump(value1));

JSONの内部にあるJSONは再帰的に変換されず、文字列としてリスト内に格納されます。なので、深いところにある値を取得するには、自力で何回か変換してやる必要があります。

// [ 1, \"two\", \"3\" ]の部分を取得し、変換します
list value2 = llJson2List(llList2String(value1, 3));

// [ 1,two,3 ]と表示されます。
llOwnerSay(dump(value2));

// { \"d\" : 2, \"e\" : false }の部分を取得し、変換します
list value3 = llJson2List(llList2String(value1, 5));

// [ d,2,e,﷗ ]と表示されます。最後の項はJSON_FALSEです
llOwnerSay(dump(value3));

llList2Json

string llList2Json(string type, list values)

LSLのリストを、JSON文字列に変換します。引数の1つめはJSONの種類(JSON_OBJECTか、JSON_ARRAY)、2つめは変換するリストです。返り値は変換したJSON文字列になります。

// JSONオブジェクトに変換してみます
string value1 = llList2Json(JSON_OBJECT, ["a", 1, "b", JSON_TRUE]);

// {"a":1,"b":true}と表示されます。JSON_TRUEはtrueに変換されます
llOwnerSay(value1);

// JSON配列に変換してみます
string value2 = llList2Json(JSON_ARRAY, [1, "two", "3"]);

// [1,"two","3"]と表示されます。ListからJSONへの変換では数値と文字列は区別されます
llOwnerSay(value2);

ちなみに、keyに数値を指定するなど、おかしな操作をするとJSON_INVALIDが返ります

// Keyに1を指定
string value3 = llList2Json(JSON_OBJECT, [1, 2]);

// JSONのKeyは文字列でないといけません
if(value3 == JSON_INVALID)
{
    llOwnerSay("不正な操作です");
}

では、ネストしたJSONを作ってみましょう。

/*
こんなJsonを作ってみます
{
    "a" : 1,
    "b" : [ 1, "two", "3" ],
    "c" : { "d" : 2, "e" : false }
}
*/

// 内部のJSON(配列)を、先に作っておきます
string subjson1 = llList2Json(JSON_ARRAY, [1, "two", "3"]);

// 内部のJSON(オブジェクト)を、先に作っておきます
string subjson2 = llList2Json(JSON_OBJECT, ["d", 2, "e", JSON_FALSE]);

// 最後にそれらを合成します
string json = llList2Json(JSON_OBJECT, ["a", 1, "b", subjson1, "c", subjson2]);

// {"a":1,"b":[1,"two","3"],"c":{"d":2,"e":false}}と表示されます
llOwnerSay(json);

小さい部品から先に作っていけばいいわけですね。ちなみにこういう書き方もできます。

// 一気に組みあげます
string json = llList2Json(JSON_OBJECT,
    [
     "a", 1,
     "b", llList2Json(JSON_ARRAY, [1, "two", "3"]),
     "c", llList2Json(JSON_OBJECT, ["d", 2, "e", JSON_FALSE])
    ]
);

// {"a":1,"b":[1,"two","3"],"c":{"d":2,"e":false}}と表示されます
llOwnerSay(json);

以上で、一括変換の説明も終わりです。

2回に渡ってLSLでJSONを扱う方法を書いてきました。JSONを扱えるようになることで、Webサービスとの連携がやりやすくなるはずです。例えばTwitterJSONを返すAPIがありますね(OpenAuthの認証が必要ですが)。しかし一方でなんでもJSONオブジェクトにしちゃったりすると、Keyの分だけ少ないLSLメモリを消費してしまうという罠もあります。本番運用では、そのへんも見極めて最適なデータ形式を選択していけるといいんじゃないかなとおもいます( ̄∇  ̄ )