Pumunta sa nilalaman

Module:JSON

Mula Wiktionary

Ang module na ito ay nag-aalok ng ilang mga pamamaraan ng palingkurang-bayan para sa pag-convert ng mga halaga ng Lua sa mga halaga ng JSON (sa mga string ng Lua na naka-encode ng UTF-8).

Sa kasamaang palad, medyo naiiba ang modelo ng data ni Lua sa JSON, kaya hindi posible na magsulat ng pangkalahatang punsyon na kumukuha ng anumang halaga ng Lua at nagbabalik ng halaga ng JSON, palaging "ginagawa ang tamang bagay". Sa halip, ang ilang mga halaga ay hindi maaaring ma-convert, at ang iba pang mga halaga ay may maraming posibleng hindi katumbas na representasyon.

Ang mga pagkakaiba ay:

  • Ang Lua ay may tatlong uri na walang JSON analogues, katulad ng function, userdata, at thread, kaya walang suporta ang modulo na ito para sa mga halaga ng mga ganitong uri.
  • Ang konsepto ng "metatables" ni Lua ay walang analogue sa JSON, kaya ganap na binabalewala ng module na ito ang mga metatable.
  • Ang uri ng numero ni Lua, gaya ng ipinatupad sa Scribunto, ay binubuo ng mga double-precision na floating-point na halaga, samantalang ang uri ng numero ng JSON ay binubuo ng mga decimal na representasyon. (At malamang na iko-convert ng end-recipient ng data ng JSON ang mga value pabalik sa isang uri ng floating-point notation.) Nangangahulugan ito na, bukod sa mga integer, sa pangkalahatan ay hindi mo maaasahang eksaktong mako-convert ang mga value. (At kahit na may mga integer, maaari mo lang asahan ang perpektong conversion sa hanay na ±109 o higit pa.) Higit pa rito, nangangahulugan ito na ang Lua ay may ilang mga numeric na halaga na walang mga JSON analogue, katulad ng positibong kawalang-hanggan, negatibong kawalang-hanggan, at "not isang numero" na mga halaga; kaya, hindi sinusuportahan ng modyul na ito ang mga halagang iyon.
  • Ang uri ng string ng Lua ay kumakatawan sa mga string ng walong-bit na byte, samantalang ang uri ng *string* ng JSON ay kumakatawan sa mga string ng mga Unicode na character. Ang module na ito ay nangangailangan ng mga string ng Lua na maging wastong mga pagkakasunod-sunod ng UTF-8.
  • Samantalang ang Lua ay mayroon lamang iisang table na uri ng pagmamapa mula sa mga arbitrary na di-nil na mga halaga hanggang sa mga di-nil na halaga, ang JSON ay may hiwalay na array' ' at bagay na uri, kung saan ang isang array ay nagmamapa mula sa isang hanay ng mga integer {0,1,…,n} sa mga arbitraryo na halaga, at ang isang bagay ay nagmamapa mula sa mga arbitraryo na string patungo sa mga arbitraryo na halaga. Bilang resulta, ang modyul na ito [TBD]

(Tandaan: ang nasa itaas ay isang pagtatangka sa isang kumpletong listahan ng mga pagkakaiba, ngunit medyo posible na may napalampas ako.)


local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")

local codepoint = m_str_utils.codepoint
local gsub = m_str_utils.gsub

local export = {}

-- Given a finite real number x, returns a string containing its JSON
-- representation, with enough precision that it *should* round-trip correctly
-- (depending on the well-behavedness of the system on the other end).
function export.json_fromNumber(x)
	if type(x) ~= "number" then
		error('Not of type "number": ' .. x .. " (" .. type(x) .. ")")
	end
	if x ~= x or x == math.huge or x == -math.huge then
		error("Not a finite real number: " .. x)
	end
	return string.format("%.17g", x)
end

-- This function makes an effort to convert an arbitrary Lua value to a string
-- containing a JSON representation of it. It's not intended to be very robust,
-- but may be useful for prototyping.
function export.toJSON(val, opts)
	opts = opts or {}
	
	local function converter(val)
		if type(val) == "nil" then
			return "null"
		elseif type(val) == "boolean" then
			return val and "true" or "false"
		elseif type(val) == "number" then
			return export.json_fromNumber(val)
		elseif type(val) == "string" then
			return export.json_fromString(val)
		elseif type(val) == "table" then
			-- If the table has a toJSON member function, call that.
			if val.toJSON then
				return val:toJSON()
			else
				return export.json_fromTable(val, converter, opts)
			end
		else
			error("Unsupported type: " .. type(val))
		end
	end
	
	return converter(val)
end

local escape_char_map = {
	["\\"] = "\\\\",
	["\""] = "\\\"",
	["\b"] = "\\b",
	["\f"] = "\\f",
	["\n"] = "\\n",
	["\r"] = "\\r",
	["\t"] = "\\t",
}

local function escape_char(c)
	return escape_char_map[c] or string.format("\\u%04X", codepoint(c))
end

-- Given a string, escapes any illegal characters and wraps it in double-quotes.
-- Raises an error if the string is not valid UTF-8.
function export.json_fromString(s)
	if type(s) ~= "string" or not mw.ustring.isutf8(s) then
		error("Not a valid UTF-8 string: " .. s)
	end
	
	-- U+2029 (LINE SEPARATOR, \226\128\168 in UTF-8)
	-- and U+2028 (PARAGRAPH SEPARATOR, \226\128\169 in UTF-8) are allowed
	-- in JSON, but must be escaped for compatibility with JavaScript.
	s = gsub(s, '[\\"%c\226\128\168\226\128\169]', escape_char)
	
	return '"' .. s .. '"'
end

-- Given a table, treats it as an array and assembles its values in the form
-- "[ v1, v2, v3 ]". Optionally takes a function to JSONify the values before
-- assembly; if that function is omitted, then the values should already be
-- strings containing valid JSON data.
function export.json_arrayFromTable(val, converter, opts)
	converter = converter or function (x) return x end
	
	-- If `val` comes from mw.loadData() then #val is always 0, so deep copy to avoid this.
	val = m_table.deepcopy(val)
	
	local ret = {}
	for i = 1, #val do
		elem = converter(val[i])
		if elem ~= nil then
			table.insert(ret, elem)
		end
	end
	
	if #ret == 0 then
		return "[]"
	end
	
	return opts and opts.compress and "[" .. table.concat(ret, ",") .. "]" or
		"[ " .. table.concat(ret, ", ") .. " ]"
end

-- Given a table whose keys are all strings, assembles its keys and values in
-- the form '{ "k1": v1, "k2": v2, "k3": v3 }'. Optionally takes a function to
-- JSONify the values before assembly; if that function is omitted, then the
-- values should already be strings containing valid JSON data. (The keys, by
-- contrast, should just be regular Lua strings; they will be passed to this
-- module's json_fromString.)
function export.json_fromTable(val, converter, opts)
	converter = converter or function (x) return x end
	
	local as_object = {}
	local string_key = false
	
	for key, value in pairs(val) do
		value = converter(value)
		
		if type(key) ~= "number" then
			string_key = true
		end
		
		if value ~= nil then
			key = export.json_fromString(tostring(key))
			table.insert(as_object, key .. (opts and opts.compress and ":" or " : ") .. value)
		end
	end
	
	if string_key then
		return "{" .. table.concat(as_object, opts and opts.compress and "," or ", ") .. "}"
	else
		return export.json_arrayFromTable(val, converter, opts)
	end
end

return export