TransWikia.com

Ways to parse JSON in LaTeX?

TeX - LaTeX Asked by user3417078 on March 21, 2021

I’m writing my recipes (food) in a small textfile in JSON. Are there any ways to parse this in LaTeX?
I know, that are lots of templates dealing with recipes, but I want to have my own style, which can be change very fast e.g. my mother is not interested in stuff like calories 🙂

Here is one simple example:

{
"recipe": {
    "title":"First recipe",
    "source":"My first cookbook",
    "carbs":"1 oz",
    "fat":"1 oz",
    "protein":"1 oz",
    "cal":"100 kcal",
    "ingredients": [
        {"item":"Eggs"},
        {"item":"Oil"},
        {"item":"Nuts"}
    ],
    "cooking": [
        {"step":"Mix eggs and oil"},
        {"step":"Add nuts"}
    ]
}
}

4 Answers

As always, I missed David's comment, so my idea is not 100% original. :) As David said in the chatroom, he subliminally gave me a hint, which proves his powers are beyond comprehension, and that ducks aren't noted for their powers of comprehension normally. :)

Warning Attention: Although my answer works, please favour Yiannis' answer since it uses a module from the standard lualibs available in LuaTeX.

I'll provide a naive answer using LuaTeX just to get things working as quickly as possible. :)

First things first: get this simple JSON encoder/decoder written in pure Lua from Jeffrey Friedl's site. The file is self-contained and it's named JSON.lua. Save to your working directory.

Jeffrey's code is released under a Creative Commons Attribution 3.0 Unported License.

At the very same level, save your JSON file as, say, recipes.json:

{
    "recipe": {
        "title":"First recipe",
        "source":"My first cookbook",
        "carbs":"1 oz",
        "fat":"1 oz",
        "protein":"1 oz",
        "cal":"100 kcal",
        "ingredients": [
            {"item":"Eggs"},
            {"item":"Oil"},
            {"item":"Nuts"}
        ],
        "cooking": [
            {"step":"Mix eggs and oil"},
            {"step":"Add nuts"}
        ]
    }
}

At last, but not least, our TeX file (e.g, recipes.tex):

documentclass{article}

usepackage{luacode}

begin{document}

begin{luacode}
function read(file)
    local handler = io.open(file, "rb")
    local content = handler:read("*all")
    handler:close()
    return content
end
JSON = (loadfile "JSON.lua")()
local table = JSON:decode(read("recipes.json"))
tex.print(table['recipe']['title'])
end{luacode}

end{document}

Run your file with

$ lualatex recipes.tex

The result:

Recipe

Enjoy! :)

Answered by Paulo Cereda on March 21, 2021

Try this:

documentclass{scrbook}
usepackage{luacode}

begin{filecontents*}{test.json}
{
"recipe": {
    "title":"First recipe",
    "source":"My first cookbook",
    "carbs":"1 oz",
    "fat":"1 oz",
    "protein":"1 oz",
    "cal":"100 kcal",
    "ingredients": [
        {"item":"Eggs"},
        {"item":"Oil"},
        {"item":"Nuts"}
    ],
    "cooking": [
        {"step":"Mix eggs and oil"},
        {"step":"Add nuts"}
    ]
}
}
end{filecontents*}

begin{document}



begin{luacode}
--  We use the lualibs built-in modules
--  this loads all the modules including a json converter
--

local M = M or {}

require("lualibs.lua")


-- @json file
function getjsonfile (file)
    local f, s
      f = io.open(file, 'r')
        s = f:read('*a')
        f.close()
        return s
 end

local s =  utilities.json.tolua(getjsonfile('test.json'))


local rep, write = string.rep, tex.print

function M.inspect (tab, offset)
   local openbracket, closebracket, par = "{", "mbox{..}}", "par"

    offset = offset or ""
    for k, v in pairs (tab) do
        local newoffset = offset .. "mbox{~~}"
        if type(v) == "table" then
           write(offset .. k .. " = " .. openbracket .. par)
           M.inspect(v, newoffset)
           write(offset .. closebracket .. par)
        else
         if k~="data" then write(offset..k.." =  ".. tostring(v), "par") 
           else
                 write(offset.."k = char data ")
           end
       end
    end
end

tex.print(M.inspect(s))
 end{luacode} 

 end{document}

It should give you this:

enter image description here

The formatting is done using an inspect method that uses an mbox{~~} to space the brackets out.

Answered by Yiannis Lazarides on March 21, 2021

Although the question is about parsing JSON in LaTeX, since the OP wants to "have my own style, which can be change very fast", I'll give a ConTeXt solution for its simplicity.

ConTeXt already comes up with a parser for JSON. To use it, simply load

usemodule[json]

Then, you can use the Lua function utilities.json.tolua to convert JSON string to Lua table and the Lua function utilities.json.tostring to convert a Lua table to a JSON string.

It is very simple to typeset Lua tables using ConTeXt Lua Document. Here is a complete example:

usemodule[json]
startluacode

  userdata = userdata or {}
  local json = utilities.json

  userdata.show_recipe = function(recipe)

  local lua_recipe  = json.tolua(recipe).recipe
  local ingredients = lua_recipe.ingredients
  local cooking     = lua_recipe.cooking

  context.subject(lua_recipe.title)

  local show_value = function(value)
    context.NC() context(value) 
    context.EQ() context(lua_recipe[value])
    context.NC() context.NR()
  end

  context.starttabulate()
    show_value("source")
    show_value("carbs")
    show_value("protein")
    show_value("cal")
  context.stoptabulate()

  context.subsubject("Ingredients")
  context.startitemize{"packed, intro"}
  for i = 1,#ingredients do
    context.startitem()
    context(ingredients[i].item)
    context.stopitem()
  end
  context.stopitemize()

  context.subsubject("Cooking")
  context.startitemize{"packed, intro"}
  for i = 1,#cooking do
    context.startitem()
    context(cooking[i].step)
    context.stopitem()
  end
  context.stopitemize()

  end
stopluacode

Now you can simply define a TeX macro to pass its argument to the Lua function.

% Note that I use the braces around #1 to make the input
% syntax slightly simpler
define[1]Recipe
    {ctxlua{userdata.show_recipe([==[{#1}]==])}}

Let's add some minimal styling to format the section heads. As with all ConTeXt documents, you can change the format by using appropriate setup... command.

setuphead[subject][style=bfb]
setuphead[subsubject][style=bfa]

Finally, the main document

starttext

Recipe
    {
      "recipe": {
          "title":"First recipe",
          "source":"My first cookbook",
          "carbs":"1 oz",
          "fat":"1 oz",
          "protein":"1 oz",
          "cal":"100 kcal",
          "ingredients": [
              {"item":"Eggs"},
              {"item":"Oil"},
              {"item":"Nuts"}
          ],
          "cooking": [
              {"step":"Mix eggs and oil"},
              {"step":"Add nuts"}
          ]
      }
    } 

stoptext

which gives

enter image description here

Answered by Aditya on March 21, 2021

Here are up to date examples that do work with TeX Live 2020 (LuaHBTeX 1.12.0). The first one is inspired by Paulo cerreda's answer but do not rely on an external tool. If you have a test.json file containing the OP recipe next to your tex document, then

% !TEX encoding = UTF-8
% !TEX program = Lualatex

documentclass{scrbook}
usepackage{luacode}
begin{document}
begin{luacode}
require("lualibs.lua")
local f = io.open('test.json', 'r')
local s = f:read('*a')
f:close()
tab =  utilities.json.tolua(s)
end{luacode} 
luadirect{tex.sprint(tab['recipe']['title'])}
end{document}

will typeset into "First recipe". Notice that tab is a global Lua variable that can be accessed later on.

The second example comes from Yiannis Lazarides answer, it has been fixed and simplified to help Lua beginners but gives the same output.

% !TEX encoding = UTF-8
% !TEX program = Lualatex
documentclass{scrbook}
usepackage{luacode}
begin{filecontents*}{test.json}
{
"recipe": {
    "title":"First recipe",
    "source":"My first cookbook",
    "carbs":"1 oz",
    "fat":"1 oz",
    "protein":"1 oz",
    "cal":"100 kcal",
    "ingredients": [
        {"item":"Eggs"},
        {"item":"Oil"},
        {"item":"Nuts"}
    ],
    "cooking": [
        {"step":"Mix eggs and oil"},
        {"step":"Add nuts"}
    ]
}
}
end{filecontents*}
begin{document}
begin{luacode}
local f = io.open('test.json', 'r')
local s = f:read('*a')
f:close()
require("lualibs.lua")
local tab =  utilities.json.tolua(s)
local openbracket = "{"
local closebracket = "mbox{..}}"
local par = "par"
local prettyprint
prettyprint = function (tab, prefix)
  for k, v in pairs (tab) do
    local newprefix = prefix .. "mbox{~~}"
    if type(v) == "table" then
      tex.print(prefix .. k .. " = " .. openbracket .. par)
      prettyprint(v, newprefix)
      tex.print(prefix .. closebracket .. par)
    elseif k == "data" then
      tex.print(prefix.."k = char data ")
    else
      tex.print(prefix..k.." =  ".. tostring(v), "par") 
    end
  end
end
prettyprint(tab, '')
end{luacode} 
end{document}

Nota Bene: there was a faulty f.close() in Yiannis' answer.

Answered by Jérôme LAURENS on March 21, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP