Browse Source

Add sjson streaming example

Fixes #2209.
Marcel Stör 6 years ago
parent
commit
555b5574a2
2 changed files with 77 additions and 2 deletions
  1. 7 2
      docs/en/modules/sjson.md
  2. 70 0
      lua_examples/sjson-streaming.lua

+ 7 - 2
docs/en/modules/sjson.md

@@ -21,6 +21,10 @@ anywhere else in your data structures. A suitable value might be `"\0"`.
 
 When encoding a Lua object, if a function is found, then it is invoked (with no arguments) and the (single) returned value is encoded in the place of the function.
 
+!!! note
+
+    All examples below use in-memory JSON or content read from the SPIFFS file system. However, where a streaming implementation really shines is in fetching large JSON structures from the remote resources and extracting values on-the-fly. An elaborate streaming example can be found in the [`/lua_examples`](../../../lua_examples/sjson-streaming.lua) folder.
+
 ## sjson.encoder()
 
 This creates an encoder object that can convert a Lua object into a JSON encoded string.
@@ -53,7 +57,8 @@ A string of up to `size` bytes, or `nil` if the encoding is complete and all dat
 #### Example
 The following example prints out (in 64 byte chunks) a JSON encoded string containing the first 4k of every file in the file system. The total string
 can be bigger than the total amount of memory on the NodeMCU.
-```
+
+```lua
 function files() 
    result = {}
    for k,v in pairs(file.list()) do
@@ -230,5 +235,5 @@ for k,v in pairs(t) do print(k,v) end
 
 ##Constants
 
-There is one constant -- `sjson.NULL` -- which is used in Lua structures to represent the presence of a JSON null.
+There is one constant, `sjson.NULL`, which is used in Lua structures to represent the presence of a JSON null.
 

+ 70 - 0
lua_examples/sjson-streaming.lua

@@ -0,0 +1,70 @@
+-- Test sjson and GitHub API
+
+local s = tls.createConnection()
+s:on("connection", function(sck, c)
+  sck:send("GET /repos/nodemcu/nodemcu-firmware/git/trees/master HTTP/1.0\r\nUser-agent: nodemcu/0.1\r\nHost: api.github.com\r\nConnection: close\r\nAccept: application/json\r\n\r\n")
+end)
+
+function startswith(String, Start)
+  return string.sub(String, 1, string.len(Start)) == Start
+end
+
+local seenBlank = false
+local partial
+local wantval = { tree = 1, path = 1, url = 1 }
+-- Make an sjson decoder that only keeps certain fields
+local decoder = sjson.decoder({
+  metatable =
+  {
+    __newindex = function(t, k, v)
+      if wantval[k] or type(k) == "number" then
+        rawset(t, k, v)
+      end
+    end
+  }
+})
+local function handledata(s)
+  decoder:write(s)
+end
+
+-- The receive callback is somewhat gnarly as it has to deal with find the end of the header
+-- and having the newline sequence split across packets
+s:on("receive", function(sck, c)
+  if partial then
+    c = partial .. c
+    partial = nil
+  end
+  if seenBlank then
+    handledata(c)
+    return
+  end
+  while c do
+    if startswith(c, "\r\n") then
+      seenBlank = true
+      c = c:sub(3)
+      handledata(c)
+      return
+    end
+    local s, e = c:find("\r\n")
+    if s then
+      -- Throw away line
+      c = c:sub(e + 1)
+    else
+      partial = c
+      c = nil
+    end
+  end
+end)
+
+local function getresult()
+  local result = decoder:result()
+  -- This gets the resulting decoded object with only the required fields
+  print(result['tree'][4]['path'], "is at",
+    result['tree'][4]['url'])
+end
+
+s:on("disconnection", getresult)
+s:on("reconnection", getresult)
+
+-- Make it all happen!
+s:connect(443, "api.github.com")