Add JSON lua module to MUSHclient distribution

Posted by Twisol on Sat 28 Aug 2010 11:53 PM — 7 posts, 29,310 views.

USA #0
Is there any chance we can get one of the pure Lua implementations of a JSON parser into the standard MUSHclient distribution, i.e. in the lua/ subdirectory? There's a decent comparison of some of the known JSON bindings (both pure Lua and not) on the Lua-Users wiki [1].

[1]: http://lua-users.org/wiki/JsonModules
Australia Forum Administrator #1
OK I added LuaJson to the distribution for 4.59.

This is a pure Lua implementation, quite fast on decoding, and which uses LPEG for its decoding logic, which appeals to me.
USA #2
Oh yea... After you made a comment about me doing an LPEG JSON module, I found it while doing the research and decided not to reinvent the wheel... I forgot to point it out tho. :)
Australia Forum Administrator #3
To be honest, I was hoping you would simplify down his 19-file implementation to one small, simple module. :)

But no matter.
USA #4
There was my pure-C binding to Jansson, but that didn't seem to go very far, so I thought I'd have better luck getting a pure-Lua solution in. Guess I was right! Thanks, Nick.
USA #5
Nick Gammon said:

To be honest, I was hoping you would simplify down his 19-file implementation to one small, simple module. :)

But no matter.


Oh, I could do that... It shouldn't take too much. The only thing that tripped me up a little was quoting unicode in strings... The quickest way to prototype escaped meta codes was with lots of little support function, and that didn't seem too elegant to me.

I'll probably do it this coming weekend (Labor Day weekend, 3 days off)... I suspect I need to revamp InfoBox for 4.59 as well.
Amended on Mon 30 Aug 2010 07:34 AM by WillFa
Australia Forum Administrator #6
Just to kick-start this, here is my preliminary attempt to do a JSON grammar in LPEG.


-- See: http://www.ietf.org/rfc/rfc4627.txt?number=4627

jsongrammar = lpeg.P {
  "JSON_text";  -- main rule name
  
  -- A JSON text is a serialized object or array.
  JSON_text = lpeg.V "object" + lpeg.V "array";
   
  -- separators
  name_separator  = lpeg.V "ws" * ":" * lpeg.V "ws"; 
  value_separator = lpeg.V "ws" * "," * lpeg.V "ws"; 
  
  -- whitespace
  ws    = ( lpeg.P " " + "\t" + "\n" + "\r" )^0;   -- zero or more of space, tab, newline, cr
  
  -- A JSON value MUST be an object, array, number, or string, or one of
  --  the following three literal names:  false, null, true
  value = lpeg.C (              -- capture values for now
          lpeg.P "false"  + 
                 "null"   + 
                 "true"   + 
          lpeg.V "object" + 
          lpeg.V "array"  + 
          lpeg.V "number" + 
          lpeg.V "string"
          );
  
  -- objects are comma-separated name/value pairs inside curly brackets 
  begin_object    = lpeg.V "ws" * "{" * lpeg.V "ws"; 
  end_object      = lpeg.V "ws" * "}" * lpeg.V "ws"; 
  
  object = lpeg.V "begin_object" * 
           ( lpeg.V "member" * ( lpeg.V "value_separator" * lpeg.V "member" )^0 )^-1 * 
           lpeg.V "end_object";
           
  -- object member is a name/value pair
  member = lpeg.V "string" * lpeg.V "name_separator" * lpeg.V "value";
  
  -- arrays are comma-separated values inside square brackets
  begin_array     = lpeg.V "ws" * "[" * lpeg.V "ws";
  end_array       = lpeg.V "ws" * "]" * lpeg.V "ws";
  
  array = lpeg.V "begin_array" * 
           ( lpeg.V "value" * ( lpeg.V "value_separator" *  lpeg.V "value" )^0 )^-1 * 
           lpeg.V "end_array";
  
  -- inside a string is \x or anything other than a quote
  char =  (lpeg.P '\\' * lpeg.P (1)) +
          (lpeg.P (1) -  '"');
  
  -- strings are "<something>"
  string = lpeg.P '"' * (lpeg.V "char"^0) * '"';
  
  -- numbers
  number    = (lpeg.P '-')^-1 * lpeg.V "int" * (lpeg.V "frac")^-1 * (lpeg.V "exp")^-1;
  digit     = lpeg.R "09";          -- any digit
  digit1_9  = lpeg.R "19";          -- digits 1 to 9
  e         = lpeg.P "e" + "E";     -- e or E
  exp       = lpeg.V "e" * (lpeg.P "-" + "+")^-1 * lpeg.V "digit"^1;  -- exponent
  frac      = lpeg.P "." * lpeg.V "digit"^1;                          -- fractional part
  int       = lpeg.P "." + (lpeg.V "digit1_9" * lpeg.V "digit"^0 );   -- integer part
  
   }    -- end of jsongrammar
           
result = lpeg.match (lpeg.Ct (jsongrammar), 
[[
 {
      "Image": {
          "Width":  800,
          "Height": 600,
          "Title":  "View from \"15th\" \r \n \\ Floor",
          "Thumbnail": {
              "Url":    "http://www.example.com/image/481989943",
              "Height": 125,
              "Width":  "100",
              "Nick": -1234e24
          },
          "IDs": [116, 943, 234, 38793]
        }
   }
]])

if result then
  print "matched!"
  tprint (result)
else
  print "no match"
end -- if


The outputting is not great, I threw in a lpeg.C (capture) around the value part, so I can see what JSON values are being output. This is the result:


matched!
1="{
          "Width":  800,
          "Height": 600,
          "Title":  "View from \"15th\" \r \n \\ Floor",
          "Thumbnail": {
              "Url":    "http://www.example.com/image/481989943",
              "Height": 125,
              "Width":  "100",
              "Nick": -1234e24
          },
          "IDs": [116, 943, 234, 38793]
        }
   "
2="800"
3="600"
4=""View from \"15th\" \r \n \\ Floor""
5="{
              "Url":    "http://www.example.com/image/481989943",
              "Height": 125,
              "Width":  "100",
              "Nick": -1234e24
          }"
6=""http://www.example.com/image/481989943""
7="125"
8=""100""
9="-1234e24"
10="[116, 943, 234, 38793]
        "
11="116"
12="943"
13="234"
14="38793"


I didn't look at the Lua JSON module (currently supplied with MUSHclient) but did look at the RFC which defines the JSON syntax, and tried to simply follow their grammar diagrams.

I gave up a bit on their fairly tedious string definitions and resorted to matching on "<something>" where the <something> is any character other than a quote, or the sequence \x where x can be anything. In practice the x should be limited to the values in the RFC (to be pedantic) and it should also allow for their somewhat imaginative idea of \uxxxx for Unicode sequences.

The outputting should be written in such a was that it outputs nice Lua tables, but this at least kicks of the grammar match.

References:

http://json.org/
http://www.ietf.org/rfc/rfc4627.txt?number=4627