Last active
March 9, 2025 03:11
-
-
Save cloudwu/3de6cacc199b12719d6cfe4fa15f7cb2 to your computer and use it in GitHub Desktop.
An inefficient but minimalist pure lua json parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- An inefficient but minimalist pure lua json parser | |
local escape_table = { | |
['"'] = '"', | |
['/'] = '/', | |
['\\'] = '\\', | |
['b'] = '\b', | |
['f'] = '\f', | |
['n'] = '\n', | |
['r'] = '\r', | |
['t'] = '\t', | |
} | |
for k,v in pairs(escape_table) do | |
escape_table[k] = "\\u" .. ("%04x"):format(v:byte()) | |
end | |
local function hextochar(hex) | |
return string.char(tonumber(hex, 16)) | |
end | |
local function decode_json(s) | |
local const_table = {} | |
local function tokenize(s) | |
s = s:gsub('\\(["/\bfnrt])', escape_table) -- unify escape | |
local n = 0 | |
s = s:gsub('"([^"]*)"', function (str) | |
str = str:gsub("\\u(....)", hextochar) | |
n = n + 1 | |
const_table[n] = str | |
return "@" .. n | |
end) | |
return s | |
end | |
s = tokenize(s) | |
s = s:gsub("[\r\n\t ]+", "") -- strip white space | |
local nest_table = { s } | |
local function nest(s) | |
local n = #nest_table | |
local function append_object(obj) | |
n = n + 1 | |
nest_table[n] = obj:sub(2,-2) | |
return "#" .. n | |
end | |
local function append_array(obj) | |
n = n + 1 | |
nest_table[n] = obj:sub(2,-2) | |
return "$" .. n | |
end | |
s = s:gsub("%b{}", append_object) | |
s = s:gsub("%b[]", append_array) | |
return s | |
end | |
local from = 1; repeat | |
local n = #nest_table | |
for i = from, n do | |
nest_table[i] = nest(nest_table[i]) | |
end | |
from = n + 1 | |
until #nest_table == n | |
local parse ; -- function decl | |
local function parse_object(s) | |
local ret = {} | |
local function parse_kv(k,v) | |
k = const_table[tonumber(k)] | |
v = parse(v) | |
ret[k] = v | |
end | |
s:gsub("@(%d+):([^,]+)", parse_kv) | |
return ret | |
end | |
local function parse_array(s) | |
local ret = {} | |
local n = 0 | |
local function parse_item(v) | |
n = n + 1 | |
ret[n] = parse(v) | |
end | |
s:gsub("[^,]+", parse_item) | |
return ret | |
end | |
local converter = { | |
["@"] = { assert, const_table }, | |
["#"] = { parse_object, nest_table }, | |
["$"] = { parse_array, nest_table }, | |
["true"] = true, | |
["false"] = true, | |
["null"] = true, | |
} | |
local const = { | |
["true"] = true, | |
["false"] = false, | |
["null"] = nil, | |
} | |
function parse(s) | |
local c = converter[s:sub(1,1)] | |
if c then | |
return c[1](c[2][tonumber(s:sub(2))]) | |
else | |
return tonumber(s) or (converter[s] or error ("Invalid constant " .. s) and const[s]) | |
end | |
end | |
return parse(nest_table[1]) | |
end | |
return decode_json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- testcase from https://github.com/DaveGamble/cJSON/ | |
local test = { | |
[[ | |
{ | |
"glossary": { | |
"title": "example glossary", | |
"GlossDiv": { | |
"title": "S", | |
"GlossList": { | |
"GlossEntry": { | |
"ID": "SGML", | |
"SortAs": "SGML", | |
"GlossTerm": "Standard Generalized Markup Language", | |
"Acronym": "SGML", | |
"Abbrev": "ISO 8879:1986", | |
"GlossDef": { | |
"para": "A meta-markup language, used to create markup languages such as DocBook.", | |
"GlossSeeAlso": ["GML", "XML"] | |
}, | |
"GlossSee": "markup" | |
} | |
} | |
} | |
} | |
} | |
]], | |
[[ | |
{"menu": { | |
"id": "file", | |
"value": "File", | |
"popup": { | |
"menuitem": [ | |
{"value": "New", "onclick": "CreateNewDoc()"}, | |
{"value": "Open", "onclick": "OpenDoc()"}, | |
{"value": "Close", "onclick": "CloseDoc()"} | |
] | |
} | |
}} | |
]], | |
[[ | |
{"widget": { | |
"debug": "on", | |
"window": { | |
"title": "Sample Konfabulator Widget", | |
"name": "main_window", | |
"width": 500, | |
"height": 500 | |
}, | |
"image": { | |
"src": "Images/Sun.png", | |
"name": "sun1", | |
"hOffset": 250, | |
"vOffset": 250, | |
"alignment": "center" | |
}, | |
"text": { | |
"data": "Click Here", | |
"size": 36, | |
"style": "bold", | |
"name": "text1", | |
"hOffset": 250, | |
"vOffset": 100, | |
"alignment": "center", | |
"onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" | |
} | |
}} | |
]], | |
[=[ | |
{"web-app": { | |
"servlet": [ | |
{ | |
"servlet-name": "cofaxCDS", | |
"servlet-class": "org.cofax.cds.CDSServlet", | |
"init-param": { | |
"configGlossary:installationAt": "Philadelphia, PA", | |
"configGlossary:adminEmail": "[email protected]", | |
"configGlossary:poweredBy": "Cofax", | |
"configGlossary:poweredByIcon": "/images/cofax.gif", | |
"configGlossary:staticPath": "/content/static", | |
"templateProcessorClass": "org.cofax.WysiwygTemplate", | |
"templateLoaderClass": "org.cofax.FilesTemplateLoader", | |
"templatePath": "templates", | |
"templateOverridePath": "", | |
"defaultListTemplate": "listTemplate.htm", | |
"defaultFileTemplate": "articleTemplate.htm", | |
"useJSP": false, | |
"jspListTemplate": "listTemplate.jsp", | |
"jspFileTemplate": "articleTemplate.jsp", | |
"cachePackageTagsTrack": 200, | |
"cachePackageTagsStore": 200, | |
"cachePackageTagsRefresh": 60, | |
"cacheTemplatesTrack": 100, | |
"cacheTemplatesStore": 50, | |
"cacheTemplatesRefresh": 15, | |
"cachePagesTrack": 200, | |
"cachePagesStore": 100, | |
"cachePagesRefresh": 10, | |
"cachePagesDirtyRead": 10, | |
"searchEngineListTemplate": "forSearchEnginesList.htm", | |
"searchEngineFileTemplate": "forSearchEngines.htm", | |
"searchEngineRobotsDb": "WEB-INF/robots.db", | |
"useDataStore": true, | |
"dataStoreClass": "org.cofax.SqlDataStore", | |
"redirectionClass": "org.cofax.SqlRedirection", | |
"dataStoreName": "cofax", | |
"dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", | |
"dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", | |
"dataStoreUser": "sa", | |
"dataStorePassword": "dataStoreTestQuery", | |
"dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", | |
"dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", | |
"dataStoreInitConns": 10, | |
"dataStoreMaxConns": 100, | |
"dataStoreConnUsageLimit": 100, | |
"dataStoreLogLevel": "debug", | |
"maxUrlLength": 500}}, | |
{ | |
"servlet-name": "cofaxEmail", | |
"servlet-class": "org.cofax.cds.EmailServlet", | |
"init-param": { | |
"mailHost": "mail1", | |
"mailHostOverride": "mail2"}}, | |
{ | |
"servlet-name": "cofaxAdmin", | |
"servlet-class": "org.cofax.cds.AdminServlet"}, | |
{ | |
"servlet-name": "fileServlet", | |
"servlet-class": "org.cofax.cds.FileServlet"}, | |
{ | |
"servlet-name": "cofaxTools", | |
"servlet-class": "org.cofax.cms.CofaxToolsServlet", | |
"init-param": { | |
"templatePath": "toolstemplates/", | |
"log": 1, | |
"logLocation": "/usr/local/tomcat/logs/CofaxTools.log", | |
"logMaxSize": "", | |
"dataLog": 1, | |
"dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", | |
"dataLogMaxSize": "", | |
"removePageCache": "/content/admin/remove?cache=pages&id=", | |
"removeTemplateCache": "/content/admin/remove?cache=templates&id=", | |
"fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", | |
"lookInContext": 1, | |
"adminGroupID": 4, | |
"betaServer": true}}], | |
"servlet-mapping": { | |
"cofaxCDS": "/", | |
"cofaxEmail": "/cofaxutil/aemail/*", | |
"cofaxAdmin": "/admin/*", | |
"fileServlet": "/static/*", | |
"cofaxTools": "/tools/*"}, | |
"taglib": { | |
"taglib-uri": "cofax.tld", | |
"taglib-location": "/WEB-INF/tlds/cofax.tld"}}} | |
]=], | |
[[ | |
{"menu": { | |
"header": "SVG Viewer", | |
"items": [ | |
{"id": "Open"}, | |
{"id": "OpenNew", "label": "Open New"}, | |
null, | |
{"id": "ZoomIn", "label": "Zoom In"}, | |
{"id": "ZoomOut", "label": "Zoom Out"}, | |
{"id": "OriginalView", "label": "Original View"}, | |
null, | |
{"id": "Quality"}, | |
{"id": "Pause"}, | |
{"id": "Mute"}, | |
null, | |
{"id": "Find", "label": "Find..."}, | |
{"id": "FindAgain", "label": "Find Again"}, | |
{"id": "Copy"}, | |
{"id": "CopyAgain", "label": "Copy Again"}, | |
{"id": "CopySVG", "label": "Copy SVG"}, | |
{"id": "ViewSVG", "label": "View SVG"}, | |
{"id": "ViewSource", "label": "View Source"}, | |
{"id": "SaveAs", "label": "Save As"}, | |
null, | |
{"id": "Help"}, | |
{"id": "About", "label": "About Adobe CVG Viewer..."} | |
] | |
}} | |
]], | |
[[ | |
[ | |
{ | |
"precision": "zip", | |
"Latitude": 37.7668, | |
"Longitude": -122.3959, | |
"Address": "", | |
"City": "SAN FRANCISCO", | |
"State": "CA", | |
"Zip": "94107", | |
"Country": "US" | |
}, | |
{ | |
"precision": "zip", | |
"Latitude": 37.371991, | |
"Longitude": -122.026020, | |
"Address": "", | |
"City": "SUNNYVALE", | |
"State": "CA", | |
"Zip": "94085", | |
"Country": "US" | |
} | |
] | |
]], | |
[[ | |
{ | |
"Image": { | |
"Width": 800, | |
"Height": 600, | |
"Title": "View from 15th Floor", | |
"Thumbnail": { | |
"Url": "http:/*www.example.com/image/481989943", | |
"Height": 125, | |
"Width": "100" | |
}, | |
"IDs": [116, 943, 234, 38793] | |
} | |
} | |
]], | |
[[ | |
[ | |
[0, -1, 0], | |
[1, 0, 0], | |
[0, 0, 1] | |
] | |
]], | |
[[ | |
["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] | |
]], | |
[[ | |
{ | |
"name": "Jack (\"Bee\") Nimble", | |
"format": {"type": "rect", | |
"width": 1920, | |
"height": 1080, | |
"interlace": false,"frame rate": 24 | |
} | |
} | |
]], | |
} | |
local decode_json = require "json" | |
local print_r = require "print_r" | |
for _, v in ipairs(test) do | |
local t = decode_json(v) | |
print_r(t) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment