diff --git a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua index 0e96a287d..0f191e522 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua @@ -335,6 +335,31 @@ empty_json_array = mk_tbl("[]", "empty_json_array") -- @see textutils.unserialiseJSON json_null = mk_tbl("null", "json_null") +local serializeJSONString +do + local function hexify(c) + return ("\\u00%02X"):format(c:byte()) + end + + local map = { + ["\""] = "\\\"", + ["\\"] = "\\\\", + ["\b"] = "\\b", + ["\f"] = "\\f", + ["\n"] = "\\n", + ["\r"] = "\\r", + ["\t"] = "\\t", + } + for i = 0, 0x1f do + local c = string.char(i) + if map[c] == nil then map[c] = hexify(c) end + end + + serializeJSONString = function(s) + return ('"%s"'):format(s:gsub("[\0-\x1f\"\\]", map):gsub("[\x7f-\xff]", hexify)) + end +end + local function serializeJSONImpl(t, tTracking, bNBTStyle) local sType = type(t) if t == empty_json_array then return "[]" @@ -361,7 +386,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle) if bNBTStyle then sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle) else - sEntry = string.format("%q", k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle) + sEntry = serializeJSONString(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle) end if nObjectSize == 0 then sObjectResult = sObjectResult .. sEntry @@ -390,7 +415,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle) end elseif sType == "string" then - return string.format("%q", t) + return serializeJSONString(t) elseif sType == "number" or sType == "boolean" then return tostring(t) diff --git a/src/test/resources/test-rom/spec/apis/textutils_spec.lua b/src/test/resources/test-rom/spec/apis/textutils_spec.lua index 94e243afc..d60d30f47 100644 --- a/src/test/resources/test-rom/spec/apis/textutils_spec.lua +++ b/src/test/resources/test-rom/spec/apis/textutils_spec.lua @@ -78,6 +78,20 @@ describe("The textutils library", function() it("serializes null", function() expect(textutils.serializeJSON(textutils.json_null)):eq("null") end) + + it("serializes strings", function() + expect(textutils.serializeJSON('a')):eq('"a"') + expect(textutils.serializeJSON('"')):eq('"\\""') + expect(textutils.serializeJSON('\\')):eq('"\\\\"') + expect(textutils.serializeJSON('/')):eq('"/"') + expect(textutils.serializeJSON('\b')):eq('"\\b"') + expect(textutils.serializeJSON('\n')):eq('"\\n"') + expect(textutils.serializeJSON(string.char(0))):eq('"\\u0000"') + expect(textutils.serializeJSON(string.char(0x0A))):eq('"\\n"') + expect(textutils.serializeJSON(string.char(0x1D))):eq('"\\u001D"') + expect(textutils.serializeJSON(string.char(0x81))):eq('"\\u0081"') + expect(textutils.serializeJSON(string.char(0xFF))):eq('"\\u00FF"') + end) end) describe("textutils.unserializeJSON", function()