JSON
Introduction
Section titled “Introduction”SLua provides the lljson library for encoding Lua values to JSON strings and decoding JSON strings back to Lua values. This is essential for data serialization, HTTP requests, and communicating with external services.
Basic Usage
Section titled “Basic Usage”Encoding to JSON
Section titled “Encoding to JSON”Convert Lua values to JSON strings with lljson.encode():
local data = { name = "Alice", age = 30, active = true}
local json = lljson.encode(data)-- json = '{"name":"Alice","age":30,"active":true}'
ll.Say(0, json)Decoding from JSON
Section titled “Decoding from JSON”Convert JSON strings back to Lua values with lljson.decode():
local json = '{"name":"Bob","score":100,"verified":false}'local data = lljson.decode(json)
ll.Say(0, data.name) -- "Bob"ll.Say(0, data.score) -- 100ll.Say(0, data.verified) -- falseData Type Mapping
Section titled “Data Type Mapping”Basic Types
Section titled “Basic Types”JSON types map to Lua types naturally:
-- Numberslljson.encode(42) -- "42"lljson.encode(3.14) -- "3.14"
-- Stringslljson.encode("hello") -- '"hello"'
-- Booleanslljson.encode(true) -- "true"lljson.encode(false) -- "false"Null Values
Section titled “Null Values”JSON null is represented by lljson.null:
-- Encoding nulllocal data = { value = lljson.null}lljson.encode(data) -- '{"value":null}'
-- Top-level nil also becomes nulllljson.encode(nil) -- "null"
-- But table keys with nil are omittedlocal obj = { present = "here", missing = nil -- This key won't appear in JSON}lljson.encode(obj) -- '{"present":"here"}'
-- Use lljson.null to explicitly include null valueslocal obj2 = { present = "here", nullable = lljson.null}lljson.encode(obj2) -- '{"present":"here","nullable":null}'When decoding, JSON null becomes lljson.null:
local data = lljson.decode('{"value":null}')-- data.value == lljson.null (true)Objects vs Arrays
Section titled “Objects vs Arrays”Objects (Dictionaries)
Section titled “Objects (Dictionaries)”Tables with string keys encode as JSON objects:
local person = { name = "Charlie", age = 25}lljson.encode(person) -- '{"name":"Charlie","age":25}'Arrays
Section titled “Arrays”Tables with numeric indices encode as JSON arrays:
local numbers = {1, 2, 3, 4, 5}lljson.encode(numbers) -- '[1,2,3,4,5]'
local fruits = {"apple", "banana", "cherry"}lljson.encode(fruits) -- '["apple","banana","cherry"]'Empty Tables
Section titled “Empty Tables”Empty tables {} encode as JSON objects by default:
lljson.encode({}) -- '{}'To encode an empty table as an array, use lljson.empty_array:
lljson.encode(lljson.empty_array) -- '[]'Or set the lljson.array_mt metatable:
local empty = {}setmetatable(empty, lljson.array_mt)lljson.encode(empty) -- '[]'Sparse Arrays
Section titled “Sparse Arrays”Arrays with gaps are padded with nulls:
local sparse = {}sparse[1] = "first"sparse[4] = "fourth"
lljson.encode(sparse) -- '["first",null,null,"fourth"]'Warning: Extremely sparse arrays (large gaps) will be rejected to prevent excessive memory usage.
Special Numeric Values
Section titled “Special Numeric Values”Infinity
Section titled “Infinity”Positive and negative infinity encode as non-standard JSON literals:
lljson.encode(math.huge) -- "1e9999"lljson.encode(-math.huge) -- "-1e9999"When decoding, these become Lua infinity values:
local data = lljson.decode("1e9999")-- data == math.huge (true)NaN (Not a Number)
Section titled “NaN (Not a Number)”NaN values encode as the non-standard JSON literal "NaN":
local nan = 0/0lljson.encode(nan) -- "NaN"When decoding:
local value = lljson.decode("NaN")-- value is NaN (check with value ~= value, which is true for NaN)Second Life Types
Section titled “Second Life Types”Keys (Strings)
Section titled “Keys (Strings)”Second Life keys (UUIDs) are strings and encode as JSON strings:
local avatar_id = "12345678-1234-1234-1234-123456789abc"lljson.encode(avatar_id) -- '"12345678-1234-1234-1234-123456789abc"'Vectors
Section titled “Vectors”Vectors encode as their string representation:
local pos = vector(1, 2.5, 3.14286)lljson.encode(pos) -- '"<1,2.5,3.14286>"'Rotations (Quaternions)
Section titled “Rotations (Quaternions)”Rotations encode as their string representation:
local rot = rotation(0, 0, 0, 1)lljson.encode(rot) -- '"<0,0,0,1>"'Buffers
Section titled “Buffers”Buffers are base64 encoded:
local buf = buffer.fromstring("Hello")lljson.encode(buf) -- Base64 encoded stringUnicode Support
Section titled “Unicode Support”LLJSON handles Unicode properly, including escape sequences:
-- Decoding Unicode escapeslocal data = lljson.decode('"\\u0048\\u0065\\u006C\\u006C\\u006F"')-- data = "Hello"
-- Encoding Unicode characterslljson.encode("Hello 世界") -- Properly encoded UTF-8Error Handling
Section titled “Error Handling”Encoding Errors
Section titled “Encoding Errors”LLJSON will raise errors for:
Mixed table keys:
-- Mixed numeric and string keys aren't allowedlocal bad = { [1] = "one", name = "test" -- Error: can't mix array and object keys}lljson.encode(bad) -- Raises errorFunctions:
local bad = { func = function() end -- Error: cannot encode functions}lljson.encode(bad) -- Raises errorSelf-referential structures:
local t = {}t.self = t -- Circular referencelljson.encode(t) -- Raises error when recursion limit exceededOversized payloads:
-- Extremely large data structures may exceed size limitsDecoding Errors
Section titled “Decoding Errors”LLJSON will raise errors for:
Invalid JSON syntax:
lljson.decode("{invalid}") -- Raises errorOversized string literals:
-- Excessively long strings may be rejectedUse pcall() to handle errors gracefully:
local success, result = pcall(lljson.decode, json_string)
if success then ll.Say(0, "Parsed successfully") -- Use resultelse ll.Say(0, "Parse error: " .. result)endAdvanced Features
Section titled “Advanced Features”Custom Serialization
Section titled “Custom Serialization”Objects with a __tojson() metamethod use custom serialization:
local Point = {}Point.__index = Point
function Point:new(x, y) local obj = {x = x, y = y} setmetatable(obj, self) return objend
function Point:__tojson() return { type = "Point", coordinates = {self.x, self.y} }end
local p = Point:new(10, 20)lljson.encode(p) -- '{"type":"Point","coordinates":[10,20]}'Forcing Array Encoding
Section titled “Forcing Array Encoding”Use lljson.array_mt to ensure table encodes as array:
local data = {10, 20, 30}setmetatable(data, lljson.array_mt)lljson.encode(data) -- '[10,20,30]'
-- Useful for guaranteeing array encoding even if table becomes emptylocal items = {}setmetatable(items, lljson.array_mt)lljson.encode(items) -- '[]' (array, not object)Nested Structures
Section titled “Nested Structures”Complex nested data structures work naturally:
local data = { users = { {name = "Alice", scores = {95, 87, 92}}, {name = "Bob", scores = {78, 85, 90}}, }, timestamp = 1234567890, metadata = { version = "1.0", verified = true }}
local json = lljson.encode(data)local decoded = lljson.decode(json)
-- Access nested valuesll.Say(0, decoded.users[1].name) -- "Alice"ll.Say(0, decoded.users[1].scores[2]) -- 87Practical Examples
Section titled “Practical Examples”Encoding Request Data
Section titled “Encoding Request Data”local request_data = { action = "update", user_id = "12345", values = { score = 100, level = 5 }}
local json = lljson.encode(request_data)ll.OwnerSay(`Request data: {json}`)Parsing Response Data
Section titled “Parsing Response Data”local response_json = `{"success": true, "message": "Update complete"}`local success, data = pcall(lljson.decode, response_json)
if success then ll.Say(0, `Response: {data.message}`)else ll.Say(0, "Failed to parse JSON response")endStoring Configuration
Section titled “Storing Configuration”-- Save configuration as JSONlocal config = { settings = { volume = 0.8, notifications = true, theme = "dark" }, user_preferences = { language = "en", timezone = "UTC" }}
local json_config = lljson.encode(config)-- Store json_config in object description or send to server
-- Later, restore configurationlocal restored = lljson.decode(json_config)ll.Say(0, "Volume: " .. restored.settings.volume)Data Exchange Between Scripts
Section titled “Data Exchange Between Scripts”-- Script 1: Encode and sendlocal message = { event = "player_joined", player = { name = "Alice", position = vector(10, 20, 30) }}
ll.MessageLinked(LINK_THIS, 0, lljson.encode(message), "")
-- Script 2: Receive and decodeLLEvents:on("link_message", function(sender, num, str, id) local success, data = pcall(lljson.decode, str)
if success then ll.Say(0, data.event .. ": " .. data.player.name) endend)Building API Responses
Section titled “Building API Responses”function createResponse(success, message, data) local response = { success = success, message = message, timestamp = os.time() }
if data then response.data = data else response.data = lljson.null end
return lljson.encode(response)end
-- Usagelocal json = createResponse(true, "Operation completed", { items_processed = 42})
ll.Say(0, json)-- '{"success":true,"message":"Operation completed","timestamp":1234567890,"data":{"items_processed":42}}'Best Practices
Section titled “Best Practices”- Always validate decoded data: Use
pcall()to catch parse errors - Be explicit about nulls: Use
lljson.nullfor JSON null values, notnil - Mark arrays explicitly: Use
lljson.array_mtfor tables that should always be arrays - Avoid circular references: Ensure your data structures don’t reference themselves
- Handle special numbers: Be aware of how infinity and NaN are represented
- Watch for encoding limitations: Functions and some metatables cannot be encoded
- Consider size limits: Very large payloads may be rejected
Common Patterns
Section titled “Common Patterns”Safe JSON Parsing
Section titled “Safe JSON Parsing”function safeJsonDecode(json_string, default_value) local success, result = pcall(lljson.decode, json_string)
if success then return result else ll.OwnerSay("JSON parse error: " .. result) return default_value endend
-- Usagelocal data = safeJsonDecode(response_body, {})Building JSON Arrays
Section titled “Building JSON Arrays”local items = {}setmetatable(items, lljson.array_mt)
for i = 1, 5 do table.insert(items, { id = i, value = "Item " .. i })end
lljson.encode(items)-- '[{"id":1,"value":"Item 1"},{"id":2,"value":"Item 2"},...]'Conditional Fields
Section titled “Conditional Fields”function encodeUser(user, include_email) local data = { id = user.id, name = user.name }
if include_email then data.email = user.email end -- If include_email is false, email field is simply omitted
return lljson.encode(data)endCommon Pitfalls
Section titled “Common Pitfalls”Empty Table Ambiguity
Section titled “Empty Table Ambiguity”-- ✗ Empty table defaults to objectlljson.encode({}) -- '{}' (might expect '[]')
-- ✓ Be explicit for arrayslljson.encode(lljson.empty_array) -- '[]'Nil vs Null
Section titled “Nil vs Null”-- ✗ nil omits the keylocal data = {key = nil}lljson.encode(data) -- '{}'
-- ✓ Use lljson.null for explicit nulllocal data = {key = lljson.null}lljson.encode(data) -- '{"key":null}'Mixed Key Types
Section titled “Mixed Key Types”-- ✗ Don't mix numeric and string keyslocal bad = { [1] = "one", name = "test"}lljson.encode(bad) -- Error
-- ✓ Use consistent key typeslocal array = {" one", "two"} -- Numeric indices onlylljson.encode(array) -- '["one","two"]'
local object = {first = "one", second = "two"} -- String keys onlylljson.encode(object) -- '{"first":"one","second":"two"}'Forgotten pcall
Section titled “Forgotten pcall”-- ✗ Unprotected decode can crash scriptlocal data = lljson.decode(untrusted_input)
-- ✓ Always protect against bad inputlocal success, data = pcall(lljson.decode, untrusted_input)if success then -- Use dataendSummary
Section titled “Summary”The lljson library provides robust JSON handling in SLua:
- Use
lljson.encode()to convert Lua values to JSON - Use
lljson.decode()to parse JSON strings lljson.nullrepresents JSON null explicitly- Empty tables default to objects; use
lljson.array_mtorlljson.empty_arrayfor arrays - Special values like infinity and NaN have non-standard representations
- Second Life types (vectors, keys) encode as strings
- Always use
pcall()when decoding untrusted JSON - Custom serialization available via
__tojson()metamethod
JSON is the standard format for data exchange, making lljson essential for HTTP communication, data storage, and inter-script messaging.
This guide covers the LLJSON library for JSON encoding and decoding in SLua.