Modul:JSONutil

Aus Wikivoyage
Dokumentation für das Modul JSONutil[Ansicht] [Bearbeiten] [Versionsgeschichte] [Aktualisieren]

Preprocess or generate JSON data

Verwendung in anderen Modulen

Dieses Modul ist notwendig für die Ausführung folgender Module. Bei Anpassungen sollte die Funktionstüchtigkeit der folgenden Module geprüft werden. Benutze dazu auch diese Tracking-Kategorie um Fehler zu finden, die sich dann auf Artikel auswirken:

Hinweise
local JSONutil = { suite  = "JSONutil",
                   serial = "2020-11-08",
                   item   = 63869449 }
--[=[
preprocess or generate JSON data
]=]



local Failsafe = JSONutil
JSONutil.Encoder  = { stab   = string.char( 9 ),
                      sep    = string.char( 10 ),
                      scream = "@error@JSONencoder@" }
JSONutil.more = 50    -- length of trailing context



local Fallback = function ()
    -- Retrieve current default language code
    --     Returns  string
    return mw.language.getContentLanguage():getCode()
                                           :lower()
end -- Fallback()



local flat = function ( adjust )
    -- Clean template argument string
    -- Parameter:
    --     adjust  -- string, or not
    -- Returns:
    --     string
    local r
    if adjust then
        r = mw.text.trim( mw.text.unstripNoWiki( adjust ) )
    else
        r = ""
    end
    return r
end -- flat()



local flip = function ( frame )
    -- Retrieve template argument indent
    -- Parameter:
    --     frame  -- object
    -- Returns:
    --     number, of indentation level, or not
    local r
    if frame.args.indent  and  frame.args.indent:match( "^%d+$" ) then
        r = tonumber( frame.args.indent )
    end
    return r
end -- flip()



JSONutil.Encoder.Array = function ( apply, adapt, alert )
    -- Convert table to JSON Array
    -- Parameter:
    --     apply  -- table, with sequence of raw elements, or
    --               string, with formatted Array, or empty
    --     adapt  -- string, with requested type, or not
    --     alert  -- true, if non-numeric elements shall trigger errors
    -- Returns:
    --     string, with JSON Array
    local r = type( apply )
    if r == "string" then
        r = mw.text.trim( apply )
        if r == "" then
            r = "[]"
        elseif r:byte( 1, 1 ) ~= 0x5B  or
               r:byte( -1, -1 ) ~= 0x5D then
            r = false
        end
    elseif r == "table" then
        local n = 0
        local strange
        for k, v in pairs( apply ) do
            if type( k ) == "number" then
                if k > n then
                    n = k
                end
            elseif alert then
                if strange then
                    strange = strange .. " "
                else
                    strange = ""
                end
                strange = strange .. tostring( k )
            end
        end -- for k, v
        if strange then
            r = string.format( "{ \"%s\": \"%s\" }",
                               JSONutil.Encoder.scream,
                               JSONutil.Encoder.string( strange ) )
        elseif n > 0 then
            local sep   = ""
            local scope = adapt or "string"
            local s
            if type( JSONutil.Encoder[ scope ] ) ~= "function" then
                scope = "string"
            end
            r = " ]"
            for i = n, 1, -1 do
                s = JSONutil.Encoder[ scope ]( apply[ i ] )
                r = string.format( "%s%s%s", s, sep, r )
                sep = ",\n  "
            end -- for i = n, 1, -1
            r = "[ " .. r
        else
            r = "[]"
        end
    else
        r = false
    end
    if not r then
        r = string.format( "[ \"%s * %s\" ]",
                           JSONutil.Encoder.scream,
                           "Bad Array" )
    end
    return r
end -- JSONutil.Encoder.Array()



JSONutil.Encoder.boolean = function ( apply )
    -- Convert string to JSON boolean
    -- Parameter:
    --     apply  -- string, with value
    -- Returns:
    --     boolean as string
    local r = mw.text.trim( apply )
    if r == ""  or  r == "null"  or  r == "false"
       or  r == "0"  or  r == "-" then
        r = "false"
    else
        r = "true"
    end
    return r
end -- JSONutil.Encoder.boolean()



JSONutil.Encoder.Component = function ( access, apply,
                                        adapt, align, alert )
    -- Create single entry for mapping object
    -- Parameter:
    --     access  -- string, with component name
    --     apply   -- component value
    --     adapt   -- string, with value type, or not
    --     align   -- number, of indentation level, or not
    --     alert   --
    -- Returns:
    --     string, with JSON fragment, and comma
    local v     = apply
    local types = adapt
    local indent, liner, scope, sep, sign
    if type( access ) == "string" then
        sign = mw.text.trim( access )
        if sign == "" then
            sign = false
        end
    end
    if type( types ) == "string" then
        types = mw.text.split( mw.text.trim( types ),  "%s+" )
    end
    if type( types ) ~= "table" then
        types = { }
        table.insert( types, "string" )
    end
    if #types == 1 then
        scope = types[ 1 ]
    else
        for i = 1, #types do
            if types[ i ] == "boolean" then
                if v == "1"  or  v == 1  or  v == true then
                    v = "true"
                    scope = "boolean"
                elseif v == "0"  or  v == 0  or  v == false then
                    v = "false"
                    scope = "boolean"
                end
                if scope then
                    types = { }
                    break    -- for i
                else
                    table.remove( types, i )
                end
            end
        end   -- for i
        for i = 1, #types do
            if types[ i ] == "number" then
                if tonumber( v ) then
                    v     = tostring( v )
                    scope = "number"
                    types = { }
                    break    -- for i
                else
                    table.remove( types, i )
                end
            end
        end    -- for i
    end
    scope = scope or "string"
    if type( JSONutil.Encoder[ scope ] ) ~= "function" then
        scope = "string"
    elseif scope == "I18N" then
        scope = "Polyglott"
    end
    if scope == "string" then
        v = v or ""
    end
    if type( align ) == "number"  and  align > 0 then
        indent = math.floor( align )
        if indent == 0 then
            indent = false
        end
    end
    if scope == "object"  or  not sign then
        liner = true
    elseif scope == "string" then
        local k = mw.ustring.len( sign ) + mw.ustring.len( v )
        if k > 60 then
            liner = true
        end
    end
    if liner then
        if indent then
            sep = "\n" .. string.rep( "  ", indent )
        else
            sep = "\n"
        end
    else
        sep = " "
    end
    if indent then
        indent = indent + 1
    end
    return  string.format( " \"%s\":%s%s,\n",
                           sign or "???",
                           sep,
                           JSONutil.Encoder[ scope ]( v, indent ) )
end -- JSONutil.Encoder.Component()



JSONutil.Encoder.Hash = function ( apply, adapt, alert )
    -- Create entries for mapping object
    -- Parameter:
    --     apply  -- table, with element value assignments
    --     adapt  -- table, with value types assignment, or not
    -- Returns:
    --     string, with JSON fragment, and comma
    local r = ""
    local s
    for k, v in pairs( apply ) do
        if type( adapt ) == "table" then
            s = adapt[ k ]
        end
        r = r .. JSONutil.Encoder.Component( tostring( k ), v, s )
    end -- for k, v
    return
end -- JSONutil.Encoder.Hash()



JSONutil.Encoder.I18N = function ( apply, align )
    -- Convert multilingual string table to JSON
    -- Parameter:
    --     apply  -- table, with mapping object
    --     align  -- number, of indentation level, or not
    -- Returns:
    --     string, with JSON object
    local r = type( apply )
    if r == "table" then
        local strange
        local fault = function ( a )
                  if strange then
                      strange = strange .. " *\n "
                  else
                      strange = ""
                  end
                  strange = strange .. a
              end
        local got, sep, indent
        for k, v in pairs( apply ) do
            if type( k ) == "string" then
                k = mw.text.trim( k )
                if type( v ) == "string" then
                    v = mw.text.trim( v )
                    if v == "" then
                        fault( string.format( "%s %s=",
                                              "Empty text", k ) )
                    end
                    if not ( k:match( "%l%l%l?" )  or
                             k:match( "%l%l%l?-%u%u" )  or
                             k:match( "%l%l%l?-%u%l%l%l+" ) ) then
                        fault( string.format( "%s %s=",
                                              "Strange language code",
                                              k ) )
                    end
                else
                    v = tostring( v )
                    fault( string.format( "%s %s=%s",
                                          "Bad type for text",
                                          k,
                                          type( v ) ) )
                end
                got = got  or  { }
                got[ k ] = v
            else
                fault( string.format( "%s %s: %s",
                                      "Bad language code type",
                                      type( k ),
                                      tostring( k ) ) )
            end
        end -- for k, v
        if not got then
            fault( "No language codes" )
            got = { }
        end
        if strange then
            got[ JSONutil.Encoder.scream ] = strange
        end
        r = false
        if type( align ) == "number"  and  align > 0 then
            indent = math.floor( align ) 
        else
            indent = 0
        end
        sep = string.rep( "  ",  indent + 1 ) 
        for k, v in pairs( got ) do
            if r then
                r = r .. ",\n"
            else
                r = ""
            end
            r = string.format( "%s  %s%s: %s",
                               r,
                               sep,
                               JSONutil.Encoder.string( k ),
                               JSONutil.Encoder.string( v ) )
        end -- for k, v
        r = string.format( "{\n%s\n%s}", r, sep )
    elseif r == "string" then
        r = JSONutil.Encoder.string( apply )
    else
        r = string.format( "{ \"%s\": \"%s: %s\" }",
                           JSONutil.Encoder.scream,
                           "Bad Lua type",
                           r )
    end
    return r
end -- JSONutil.Encoder.I18N()



JSONutil.Encoder.number = function ( apply )
    -- Convert string to JSON number
    -- Parameter:
    --     apply  -- string, with presumable number
    -- Returns:
    --     number, or "NaN"
    local s = mw.text.trim( apply )
    JSONutil.Encoder.minus = JSONutil.Encoder.minus  or
                             mw.ustring.char( 0x2212 )
    s = s:gsub( JSONutil.Encoder.minus, "-" )
    return tonumber( s:lower() )  or  "NaN"
end -- JSONutil.Encoder.number()



JSONutil.Encoder.object = function ( apply, align )
    -- Create mapping object
    -- Parameter:
    --     apply  -- string, with components, may end with comma
    --     align  -- number, of indentation level, or not
    -- Returns:
    --     string, with JSON fragment
    local story = mw.text.trim( apply )
    local start = ""
    if story:sub( -1 ) == "," then
        story = story:sub( 1, -2 )
    end
    if type( align ) == "number"  and  align > 0 then
        local indent = math.floor( align )
        if indent > 0 then
            start = string.rep( "  ", indent )
        end
    end
    return  string.format( "%s{ %s\n%s}", start, story, start )
end -- JSONutil.Encoder.object()



JSONutil.Encoder.Polyglott = function ( apply, align )
    -- Convert string or multilingual string table to JSON
    -- Parameter:
    --     apply  -- string, with string or object
    --     align  -- number, of indentation level, or not
    -- Returns:
    --     string
    local r = type( apply )
    if r == "string" then
        r = mw.text.trim( apply )
        if not r:match( "^{%s*\"" )  or
           not r:match( "\"%s*}$" ) then
            r = JSONutil.Encoder.string( r )
        end
    else
        r = string.format( "{ \"%s\": \"%s: %s\" }",
                           JSONutil.Encoder.scream,
                           "Bad Lua type",
                           r )
    end
    return r
end -- JSONutil.Encoder.Polyglott()



JSONutil.Encoder.string = function ( apply )
    -- Convert plain string to strict JSON string
    -- Parameter:
    --     apply  -- string, with plain string
    -- Returns:
    --     string, with quoted trimmed JSON string
    return  string.format( "\"%s\"",
                           mw.text.trim( apply )
                                  :gsub( "\\",  "\\\\" )
                                  :gsub( "\"",  "\\\"" )
                                  :gsub( JSONutil.Encoder.sep,  "\\n" )
                                  :gsub( JSONutil.Encoder.stab, "\\t" ) )
end -- JSONutil.Encoder.string()



JSONutil.fair = function ( apply )
    -- Reduce enhanced JSON data to strict JSON
    -- Parameter:
    --     apply  -- string, with enhanced JSON
    -- Returns:
    --     1    -- string|nil|false, with error keyword
    --     2    -- string, with JSON or context
    local m   = 0
    local n   = 0
    local s   = mw.text.trim( apply )
    local i, j, last, r, scan, sep0, sep1, start, stub, suffix
    local framework = function ( a )
                              -- syntax analysis outside strings
                              local k = 1
                              local c
                              while k do
                                  k = a:find( "[{%[%]}]", k )
                                  if k then
                                      c = a:byte( k, k )
                                      if c == 0x7B then    -- {
                                          m = m + 1
                                      elseif c == 0x7D then    -- }
                                          m = m - 1
                                      elseif c == 0x5B then    -- [
                                          n = n + 1
                                      else    -- ]
                                          n = n - 1
                                      end
                                      k = k + 1
                                  end
                              end   -- while k
                      end    -- framework()
    local free = function ( a, at, f )
                     -- Throws: error if /* is not matched by */
                     local s = a
                     local i = s:find( "//", at, true )
                     local k = s:find( "/*", at, true )
                     if i or k then
                         local m = s:find( sep0, at )
                         if i   and   ( not m  or  i < m ) then
                             k = s:find( "\n",  i + 2,  true )
                             if k then
                                 if i == 1 then
                                     s = s:sub( k + 1 )
                                 else
                                     s = s:sub( 1,  i - 1 )   ..
                                         s:sub( k + 1 )
                                 end
                             elseif i > 1 then
                                 s = s:sub( 1,  i - 1 )
                             else
                                 s = ""
                             end
                         elseif k   and   ( not m  or  k < m ) then
                             i = s:find( "*/",  k + 2,  true )
                             if i then
                                 if k == 1 then
                                     s = s:sub( i + 2 )
                                 else
                                     s = s:sub( 1,  k - 1 )   ..
                                         s:sub( i + 2 )
                                 end
                             else
                                 error( s:sub( k + 2 ), 0 )
                             end
                             i = k
                         else
                             i = false
                         end
                         if i then
                             s = mw.text.trim( s )
                             if s:find( "/", 1, true ) then
                                 s = f( s, i, f )
                             end
                         end
                     end
                     return s
                 end    -- free()
    if s:sub( 1, 1 ) == '{' then
        s    = s:gsub( string.char( 13, 10 ),  JSONutil.Encoder.sep )
                :gsub( string.char( 13 ),  JSONutil.Encoder.sep )
        stub = s:gsub( JSONutil.Encoder.sep, "" )
                :gsub( JSONutil.Encoder.stab, "" )
        scan = string.char( 0x5B, 0x01, 0x2D, 0x1F, 0x5D )    -- [ \-\ ]
        j    = stub:find( scan )
        if j then
            r = "ControlChar"
            s = mw.text.trim( s:sub( j + 1 ) )
            s = mw.ustring.sub( s, 1, JSONutil.more )
        else
            i    = true
            j    = 1
            last = ( stub:sub( -1 ) == "}" )
            sep0 = string.char( 0x5B, 0x22, 0x27, 0x5D )    -- [ " ' ]
            sep1 = string.char( 0x5B, 0x5C, 0x22, 0x5D )    -- [ \ " ]
        end
    else
        r = "Bracket0"
        s = mw.ustring.sub( s, 1, JSONutil.more )
    end
    while i do
        i, s = pcall( free, s, j, free )
        if i then
            i = s:find( sep0, j )
        else
            r = "CommentEnd"
            s = mw.text.trim( s )
            s = mw.ustring.sub( s, 1, JSONutil.more )
        end
        if i then
            if j == 1 then
                framework( s:sub( 1, i - 1 ) )
            end
            if s:sub( i, i ) == '"' then
                stub = s:sub( j,  i - 1 )
                if stub:find( '[^"]*,%s*[%]}]' ) then
                    r = "CommaEnd"
                    s = mw.text.trim( stub )
                    s = mw.ustring.sub( s, 1, JSONutil.more )
                    i = false
                    j = false
                else
                    if j > 1 then
                        framework( stub )
                    end
                    i = i + 1
                    j = i
                end
                while j do
                    j = s:find( sep1, j )
                    if j then
                        if s:sub( j, j ) == '"' then
                            start  = s:sub( 1,  i - 1 )
                            suffix = s:sub( j )
                            if j > i then
                                stub = s:sub( i,  j - 1 )
                                        :gsub( JSONutil.Encoder.sep,
                                               "\\n" )
                                        :gsub( JSONutil.Encoder.stab,
                                               "\\t" )
                                j = i + stub:len()
                                s = string.format( "%s%s%s",
                                                   start, stub, suffix )
                            else
                                s = start .. suffix
                            end
                            j = j + 1
                            break   -- while j
                        else
                            j = j + 2
                        end
                    else
                        r = "QouteEnd"
                        s = mw.text.trim( s:sub( i ) )
                        s = mw.ustring.sub( s, 1, JSONutil.more )
                        i = false
                    end
                end   -- while j
            else
                r = "Qoute"
                s = mw.text.trim( s:sub( i ) )
                s = mw.ustring.sub( s, 1, JSONutil.more )
                i = false
            end
        elseif not r then
            stub = s:sub( j )
            if stub:find( '[^"]*,%s*[%]}]' ) then
                r = "CommaEnd"
                s = mw.text.trim( stub )
                s = mw.ustring.sub( s, 1, JSONutil.more )
            else
                framework( stub )
            end
        end
    end   -- while i
    if not r   and   ( m ~= 0  or  n ~= 0 ) then
        if m ~= 0 then
            s = "}"
            if m > 0 then
                r = "BracketCloseLack"
                j = m
            elseif m < 0 then
                r = "BracketClosePlus"
                j = -m
            end
        else
            s = "]"
            if n > 0 then
                r = "BracketCloseLack"
                j = n
            else
                r = "BracketClosePlus"
                j = -n
            end
        end
        if j > 1 then
            s =  string.format( "%d %s", j, s )
        end
    elseif not ( r or last ) then
        stub = suffix or apply or ""
        j    = stub:find( "/", 1, true )
        if j then
            i, stub = pcall( free, stub, j, free )
        else
            i = true
        end
        stub = mw.text.trim( stub )
        if i then
            if stub:sub( - 1 ) ~= "}" then
                r = "Trailing"
                s = stub:match( "%}%s*(%S[^%}]*)$" )
                if s then
                    s = mw.ustring.sub( s, 1, JSONutil.more )
                else
                    s = mw.ustring.sub( stub,  - JSONutil.more )
                end
            end
        else
            r = "CommentEnd"
            s = mw.ustring.sub( stub, 1, JSONutil.more )
        end
    end
    if r and s then
        s = s:gsub( JSONutil.Encoder.sep,  " " )
        s = mw.text.encode( s ):gsub( "|", "&#124;" )
    end
    return r, s
end -- JSONutil.fair()



JSONutil.fault = function ( alert, add, adapt )
    -- Retrieve formatted message
    -- Parameter:
    --     alert  -- string, with error keyword, or other text
    --     add    -- string|nil|false, with context
    --     adapt  -- function|string|table|nil|false, for I18N
    -- Returns string, with HTML span
    local e = mw.html.create( "span" )
                     :addClass( "error" )
    local s = alert
    if type( s ) == "string" then
        s = mw.text.trim( s )
        if s == "" then
            s = "EMPTY JSONutil.fault key"
        end
        if not s:find( " ", 1, true ) then
            local storage = string.format( "I18n/Module:%s.tab",
                                           JSONutil.suite )
            local lucky, t = pcall( mw.ext.data.get, storage, "_" )
            if type( t ) == "table" then
                t = t.data
                if type( t ) == "table" then
                    local e
                    s = "err_" .. s
                    for i = 1, #t do
                        e = t[ i ]
                        if type( e ) == "table" then
                            if e[ 1 ] == s then
                                e = e[ 2 ]
                                if type( e ) == "table" then
                                    local q = type( adapt )
                                    if q == "function" then
                                        s = adapt( e, s )
                                        t = false
                                    elseif q == "string" then
                                        t = mw.text.split( adapt, "%s+" )
                                    elseif q == "table" then
                                        t = adapt
                                    else
                                        t = { }
                                    end
                                    if t then
                                        table.insert( t, Fallback() )
                                        table.insert( t, "en" )
                                        for k = 1, #t do
                                            q = e[ t[ k ] ]
                                            if type( q ) == "string" then
                                                s = q
                                                break   -- for k
                                            end
                                        end   -- for k
                                    end
                                else
                                    s = "JSONutil.fault I18N bad #" ..
                                        tostring( i )
                                end
                                break   -- for i
                            end
                        else
                            break   -- for i
                        end
                    end   -- for i
                else
                    s = "INVALID JSONutil.fault I18N corrupted"
                end
            else
                s = "INVALID JSONutil.fault commons:Data: " .. type( t )
            end
        end
    else
        s = "INVALID JSONutil.fault key: " .. tostring( s )
    end
    if type( add ) == "string" then
        s = string.format( "%s &#8211; %s", s, add )
    end
    e:wikitext( s )
    return tostring( e )
end -- JSONutil.fault()



JSONutil.fetch = function ( apply, always, adapt )
    -- Retrieve JSON data, or error message
    -- Parameter:
    --     apply   -- string, with presumable JSON text
    --     always  -- true, if apply is expected to need preprocessing
    --     adapt   -- function|string|table|nil|false, for I18N
    -- Returns table, with data, or string, with error as HTML span
    local lucky, r
    if not always then
        lucky, r = pcall( mw.text.jsonDecode, apply )
    end
    if not lucky then
        lucky, r = JSONutil.fair( apply )
        if lucky then
            r = JSONutil.fault( lucky, r, adapt )
        else
            lucky, r = pcall( mw.text.jsonDecode, r )
            if not lucky then
                r = JSONutil.fault( r, false, adapt )
            end
        end
    end
    return r
end -- JSONutil.fetch()



Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version
    --                         or "wikidata" or "~" or "@" or false
    -- Postcondition:
    --     Returns  string  -- with queried version/item, also if problem
    --              false   -- if appropriate
    -- 2020-08-17
    local since = atleast
    local last    = ( since == "~" )
    local linked  = ( since == "@" )
    local link    = ( since == "item" )
    local r
    if last  or  link  or  linked  or  since == "wikidata" then
        local item = Failsafe.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local suited = string.format( "Q%d", item )
            if link then
                r = suited
            else
                local entity = mw.wikibase.getEntity( suited )
                if type( entity ) == "table" then
                    local seek = Failsafe.serialProperty or "P348"
                    local vsn  = entity:formatPropertyValues( seek )
                    if type( vsn ) == "table"  and
                       type( vsn.value ) == "string"  and
                       vsn.value ~= "" then
                        if last  and  vsn.value == Failsafe.serial then
                            r = false
                        elseif linked then
                            if mw.title.getCurrentTitle().prefixedText
                               ==  mw.wikibase.getSitelink( suited ) then
                                r = false
                            else
                                r = suited
                            end
                        else
                            r = vsn.value
                        end
                    end
                end
            end
        end
    end
    if type( r ) == "nil" then
        if not since  or  since <= Failsafe.serial then
            r = Failsafe.serial
        else
            r = false
        end
    end
    return r
end -- Failsafe.failsafe()



-- Export
local p = { }

p.failsafe = function ( frame )
    -- Versioning interface
    local s = type( frame )
    local since
    if s == "table" then
        since = frame.args[ 1 ]
    elseif s == "string" then
        since = frame
    end
    if since then
        since = mw.text.trim( since )
        if since == "" then
            since = false
        end
    end
    return Failsafe.failsafe( since )  or  ""
end -- p.failsafe


p.encodeArray = function ( frame )
    return JSONutil.Encoder.Array( frame:getParent().args,
                                   frame.args.type,
                                   frame.args.error == "1" )
end -- p.encodeArray


p.encodeComponent = function ( frame )
    return JSONutil.Encoder.Component( frame.args.sign,
                                       frame.args.value,
                                       frame.args.type,
                                       flip( frame ),
                                       frame.args.error == "1" )
end -- p.encodeComponent


p.encodeHash = function ( frame )
    return JSONutil.Encoder.Hash( frame:getParent().args,
                                  frame.args )
end -- p.encodeHash


p.encodeI18N = function ( frame )
    return JSONutil.Encoder.I18N( frame:getParent().args,
                                  flip( frame ) )
end -- p.encodeI18N


p.encodeObject = function ( frame )
    return JSONutil.Encoder.object( flat( frame.args[ 1 ] ),
                                    flip( frame ) )
end -- p.encodeObject


p.encodePolyglott = function ( frame )
    return JSONutil.Encoder.Polyglott( flat( frame.args[ 1 ] ),
                                       flip( frame ) )
end -- p.encodePolyglott


p.JSONutil = function ()
    -- Module interface
    return JSONutil
end

return p