texlive[74897] Master/texmf-dist: jsonparse (9apr25)

commits+karl at tug.org commits+karl at tug.org
Wed Apr 9 21:58:18 CEST 2025


Revision: 74897
          https://tug.org/svn/texlive?view=revision&revision=74897
Author:   karl
Date:     2025-04-09 21:58:17 +0200 (Wed, 09 Apr 2025)
Log Message:
-----------
jsonparse (9apr25)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/latex/jsonparse/README.md
    trunk/Master/texmf-dist/doc/latex/jsonparse/jsonparse-doc.pdf
    trunk/Master/texmf-dist/doc/latex/jsonparse/jsonparse-doc.tex
    trunk/Master/texmf-dist/tex/latex/jsonparse/jsonparse.sty

Modified: trunk/Master/texmf-dist/doc/latex/jsonparse/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/jsonparse/README.md	2025-04-09 19:58:08 UTC (rev 74896)
+++ trunk/Master/texmf-dist/doc/latex/jsonparse/README.md	2025-04-09 19:58:17 UTC (rev 74897)
@@ -1,4 +1,4 @@
-![Version 1.3.1](https://img.shields.io/badge/version-1.3.1-blue)
+![Version 1.4.0](https://img.shields.io/badge/version-1.4.0-blue)
 
 ![Jason, the JSON parsing horse](https://github.com/jasperhabicht/jsonparse/assets/6378801/ddfddc70-bf5f-4121-ba45-4b9128875d85)
 

Modified: trunk/Master/texmf-dist/doc/latex/jsonparse/jsonparse-doc.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/jsonparse/jsonparse-doc.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/jsonparse/jsonparse-doc.tex	2025-04-09 19:58:08 UTC (rev 74896)
+++ trunk/Master/texmf-dist/doc/latex/jsonparse/jsonparse-doc.tex	2025-04-09 19:58:17 UTC (rev 74897)
@@ -11,8 +11,8 @@
 % This work has the LPPL maintenance status `maintained'.
 %
 \documentclass[a4paper]{article}
-\def\jsonparsefileversion{1.3.1}
-\def\jsonparsefiledate{30 March 2025}
+\def\jsonparsefileversion{1.4.0}
+\def\jsonparsefiledate{10 April 2025}
 
 \usepackage[T1]{fontenc}
 \usepackage{Alegreya}
@@ -36,23 +36,23 @@
 \makeatletter
 \renewcommand*{\thefootnote}{\fnsymbol{footnote}}
 \renewcommand{\@makefntext}[1]{%
-    \noindent\hbox to 0.75em{\normalfont\@thefnmark\hss}~#1%
+  \noindent\hbox to 0.75em{\normalfont\@thefnmark\hss}~#1%
 }
 \makeatother
 
 \tcbuselibrary{skins,listings}
 \lstdefinestyle{jsonparsedocmacro}{
-    basicstyle=\small\ttfamily,
-    literate=*{<}{{{\color{black!50}\guilsinglleft}}}1
-        {>}{{{\color{black!50}\guilsinglright}}}1,
-    keywords={},
-    moredelim=[is][\bfseries]{|}{|},
-    moredelim=[is][\bfseries\itshape]{?}{?},
-    moredelim=[is][\color{black!50}]{!}{!},
+  basicstyle=\small\ttfamily,
+  literate=*{<}{{{\color{black!50}\guilsinglleft}}}1
+    {>}{{{\color{black!50}\guilsinglright}}}1,
+  keywords={},
+  moredelim=[is][\bfseries]{|}{|},
+  moredelim=[is][\bfseries\itshape]{?}{?},
+  moredelim=[is][\color{black!50}]{!}{!},
 }
 \lstdefinestyle{jsonparsedoccodeexample}{
-    basicstyle=\small\ttfamily,
-    keywords={},
+  basicstyle=\small\ttfamily,
+  keywords={},
 }
 
 \hypersetup{colorlinks}
@@ -60,61 +60,61 @@
 \ExplSyntaxOn
 \int_new:N \l_jsonparse_doc_change_int
 \NewDocumentCommand{\changes}{ m m m }{
-    \int_incr:N \l_jsonparse_doc_change_int
-    \prop_new:c { l_jsonparse_doc_change_
-        \int_to_roman:n { \l_jsonparse_doc_change_int } _prop }
-    \prop_put:cnn { l_jsonparse_doc_change_
-        \int_to_roman:n { \l_jsonparse_doc_change_int } _prop } { version } {#1}
-    \prop_put:cnn { l_jsonparse_doc_change_
-        \int_to_roman:n { \l_jsonparse_doc_change_int } _prop } { date } {#2}
-    \prop_put:cnn { l_jsonparse_doc_change_
-        \int_to_roman:n { \l_jsonparse_doc_change_int } _prop } { changes } {#3}
+  \int_incr:N \l_jsonparse_doc_change_int
+  \prop_new:c { l_jsonparse_doc_change_
+    \int_to_roman:n { \l_jsonparse_doc_change_int } _prop }
+  \prop_put:cnn { l_jsonparse_doc_change_
+    \int_to_roman:n { \l_jsonparse_doc_change_int } _prop } { version } {#1}
+  \prop_put:cnn { l_jsonparse_doc_change_
+    \int_to_roman:n { \l_jsonparse_doc_change_int } _prop } { date } {#2}
+  \prop_put:cnn { l_jsonparse_doc_change_
+    \int_to_roman:n { \l_jsonparse_doc_change_int } _prop } { changes } {#3}
 }
 \NewDocumentCommand{\printchanges}{ }{
-    \section{Changes}
-    \begin{description}
-        \setlength\itemsep{0pt}
-        \int_step_inline:nn { \l_jsonparse_doc_change_int } {
-            \item[
-                \prop_item:cn { l_jsonparse_doc_change_
-                \int_to_roman:n { ##1 } _prop } { version } ~
-                \normalfont{ (
-                    \prop_item:cn { l_jsonparse_doc_change_
-                    \int_to_roman:n { ##1 } _prop } { date }
-                ) }
-            ]
-            \prop_item:cn { l_jsonparse_doc_change_
-            \int_to_roman:n { ##1 } _prop } { changes }
-        }
-    \end{description}
+  \section{Changes}
+  \begin{description}
+    \setlength\itemsep{0pt}
+    \int_step_inline:nn { \l_jsonparse_doc_change_int } {
+      \item[
+        \prop_item:cn { l_jsonparse_doc_change_
+        \int_to_roman:n { ##1 } _prop } { version } ~
+        \normalfont{ (
+          \prop_item:cn { l_jsonparse_doc_change_
+          \int_to_roman:n { ##1 } _prop } { date }
+        ) }
+      ]
+      \prop_item:cn { l_jsonparse_doc_change_
+      \int_to_roman:n { ##1 } _prop } { changes }
+    }
+  \end{description}
 }
 
 \tl_new:N \l_jsonparse_doc_doctitle_tl
 \NewExpandableDocumentCommand{\makedoctitle}{ o m o m m o m }{
-    \hypersetup{
-        pdfauthor={#5},
-        pdftitle={\IfValueTF{#1}{#1}{#2}},
-        pdfsubject={\IfValueTF{#3}{#3}{#4}}
-    }
-    \tl_set:Nn \l_jsonparse_doc_doctitle_tl {
-        \group_begin:
-            \tcbset{
-                title ~ style ~ hook/.style={
-                    boxrule=2pt,
-                    fontupper=\huge\ttfamily
-                }
-            }
-            \setlength{\parindent}{0pt}\sffamily
-            \Huge{\bfseries #2}\par\bigskip
-            \Large #4\par\bigskip
-            \large #5
-            \IfValueT{#6}{
-                \,\footnote{#6}\par\bigskip
-            }
-            #7\par\bigskip
-            \rule{\textwidth}{.08em}
-        \group_end:
-    }
+  \hypersetup{
+    pdfauthor={#5},
+    pdftitle={\IfValueTF{#1}{#1}{#2}},
+    pdfsubject={\IfValueTF{#3}{#3}{#4}}
+  }
+  \tl_set:Nn \l_jsonparse_doc_doctitle_tl {
+    \group_begin:
+      \tcbset{
+        title ~ style ~ hook/.style={
+          boxrule=2pt,
+          fontupper=\huge\ttfamily
+        }
+      }
+      \setlength{\parindent}{0pt}\sffamily
+      \Huge{\bfseries #2}\par\bigskip
+      \Large #4\par\bigskip
+      \large #5
+      \IfValueT{#6}{
+        \,\footnote{#6}\par\bigskip
+      }
+      #7\par\bigskip
+      \rule{\textwidth}{.08em}
+    \group_end:
+  }
 }
 
 \NewExpandableDocumentCommand{\printdoctitle}{ }{
@@ -134,118 +134,124 @@
   \tikz{\expandablestarcode}
 }
 
+\NewDocumentCommand{\warning}{}{%
+  \begin{tikzpicture}[overlay, baseline={(w.base)}]
+    \node[circle, fill, black!25!red!10, text=black!25!red]
+      (w) at (-3em,0pt) {\bfseries !};
+  \end{tikzpicture}%
+}
+
 \tcbset{
-    size=small,
-    arc=2.5pt,
-    outer arc=2.5pt,
-    colframe=black!10,
-    colback=black!10,
-    title style hook/.style={},
-    optional hypertarget/.code={
-      \IfValueT{#1}{\pgfkeysalso{hypertarget=#1}}
-    }
+  size=small,
+  arc=2.5pt,
+  outer arc=2.5pt,
+  colframe=black!10,
+  colback=black!10,
+  title style hook/.style={},
 }
 
 \NewTCBListing{macrodef}{ s o }{
-    listing only,
-    listing style={jsonparsedocmacro},
-    grow to left by=2cm,
-    boxrule=0pt,
-    after={\par\smallskip\noindent},
-    enhanced,
-    optional hypertarget={#2},
+  listing only,
+  listing style={jsonparsedocmacro},
+  grow to left by=2cm,
+  boxrule=0pt,
+  after={\par\smallskip\noindent},
+  enhanced,
+  IfValueT={#2}{
+    hypertarget=#2
+  },
+  IfBooleanT={#1}{
     overlay={
-      \IfBooleanT{#1}{
-        \expandablestarcode[frame.north east]
-      }
+      \expandablestarcode[frame.north east]
     }
+  }
 }
 
 \NewTCBListing{codeexample}{ }{
-    listing only,
-    listing style={jsonparsedoccodeexample},
-    after={\par\smallskip\noindent}
+  listing only,
+  listing style={jsonparsedoccodeexample},
+  after={\par\smallskip\noindent}
 }
 
 \NewTCBListing{codeexamplecolumns}{ }{
-    text side listing,
-    lefthand width=4.5cm,
-    listing style={jsonparsedoccodeexample},
-    after={\par\smallskip\noindent}
+  text side listing,
+  lefthand width=4.5cm,
+  listing style={jsonparsedoccodeexample},
+  after={\par\smallskip\noindent}
 }
 
 \NewTCBListing{macrodeprecated}{ s }{
-    listing only,
-    listing style={jsonparsedocmacro},
-    boxrule=0pt,
-    width={0.5\linewidth-2.5pt},
-    box align=top,
-    after=\hfill,
-    colframe=black!25!red!10,
-    colback=black!25!red!10,
-    enhanced,
+  listing only,
+  listing style={jsonparsedocmacro},
+  boxrule=0pt,
+  width={0.5\linewidth-2.5pt},
+  box align=top,
+  after=\hfill,
+  colframe=black!25!red!10,
+  colback=black!25!red!10,
+  enhanced,
+  IfBooleanT={#1}{
     overlay={
-      \IfBooleanT{#1}{
-        \fill[black!25!red!10]
-          ([yshift={5pt-1.5em}]frame.north east)
-          -- ++(5pt,-5pt) -- ++(-5pt,-5pt)
-          -- cycle;
-      }
+      \fill[black!25!red!10]
+        ([yshift={5pt-1.5em}]frame.north east)
+        -- ++(5pt,-5pt) -- ++(-5pt,-5pt)
+        -- cycle;
     }
+  }
 }
 
 \NewTCBListing{macroreplacement}{ }{
-    listing only,
-    listing style={jsonparsedocmacro},
-    boxrule=0pt,
-    width={0.5\linewidth-2.5pt},
-    box align=top,
-    before={},
-    colframe=black!25!green!10,
-    colback=black!25!green!10
+  listing only,
+  listing style={jsonparsedocmacro},
+  boxrule=0pt,
+  width={0.5\linewidth-2.5pt},
+  box align=top,
+  before={},
+  colframe=black!25!green!10,
+  colback=black!25!green!10
 }
 
 \NewTotalTCBox{\macro}{ O{} v }{
-    verbatim,
-    size=tight,
-    arc=1pt,
-    outer arc=1pt,
-    top=0.5pt,
-    bottom=0.5pt,
-    left=2pt,
-    right=2pt,
-    boxrule=0pt,
-    colframe=black!10,
-    colback=black!10,
-    fontupper=\small\ttfamily,
-    #1
+  verbatim,
+  size=tight,
+  arc=1pt,
+  outer arc=1pt,
+  top=0.5pt,
+  bottom=0.5pt,
+  left=2pt,
+  right=2pt,
+  boxrule=0pt,
+  colframe=black!10,
+  colback=black!10,
+  fontupper=\small\ttfamily,
+  #1
 }{\vphantom{/g}\lstinline^#2^}
 
 \NewTotalTCBox{\titlemacro}{ s O{} m }{
-    verbatim,
-    size=tight,
-    arc=1pt,
-    outer arc=1pt,
-    top=0.5pt,
-    bottom=0.5pt,
-    left=2pt,
-    right=2pt,
-    boxrule=0pt,
-    colframe=black!10,
-    colback=black!10,
-    fontupper=\small\ttfamily,
-    title style hook,
-    #2
+  verbatim,
+  size=tight,
+  arc=1pt,
+  outer arc=1pt,
+  top=0.5pt,
+  bottom=0.5pt,
+  left=2pt,
+  right=2pt,
+  boxrule=0pt,
+  colframe=black!10,
+  colback=black!10,
+  fontupper=\small\ttfamily,
+  title style hook,
+  #2
 }{\vphantom{/g}\IfBooleanT{#1}{\textbackslash}#3}
 
 \usepackage{jsonparse}
 
 \makedoctitle
-    [The jsonparse package]
-    {The \titlemacro{jsonparse} package}
-    {A handy way to parse, store and access JSON data from files or strings in LaTeX documents}
-    {Jasper Habicht}[E-mail: \href{mailto:mail at jasperhabicht.de}{mail at jasperhabicht.de}. I am grateful to Joseph Wright, Jonathan P. Spratte and David Carlisle who helped me navigating the peculiarities of TeX and optimizing the code. Jason, the JSON parsing horse: \textcopyright{} 2024--2025 Hannah Klöber.]
-    {Version \jsonparsefileversion, released on \jsonparsefiledate}
+  [The jsonparse package]
+  {The \titlemacro{jsonparse} package}
+  {A handy way to parse, store and access JSON data from files or strings in LaTeX documents}
+  {Jasper Habicht}[E-mail: \href{mailto:mail at jasperhabicht.de}{mail at jasperhabicht.de}. I am grateful to Joseph Wright, Jonathan P. Spratte and David Carlisle who helped me navigating the peculiarities of TeX and optimizing the code. Jason, the JSON parsing horse: \textcopyright{} 2024--2025 Hannah Klöber.]
+  {Version \jsonparsefileversion, released on \jsonparsefiledate}
 
 %\changes{v0.3.0}{2024/04/08}{First public beta release.}
 \changes{v0.5.0}{2024/04/09}{Changed from string token variables to token lists to support Unicode.}
@@ -284,7 +290,9 @@
 %\changes{v1.2.2}{2025/02/25}{Fixes in documentation.}
 \changes{v1.2.3}{2025/03/23}{Enabled nesting of mapped inline function.}
 \changes{v1.3.0}{2025/03/25}{Enhancements in key setting mechanism.}
-%\changes{v1.3.1}{2025/02/30}{Fixes in documentation. Bug fixes.}
+%\changes{v1.3.1}{2025/03/30}{Fixes in documentation. Bug fixes.}
+%\changes{v1.3.2}{2025/04/07}{Bug fixes and code improvements.}
+\changes{v1.4.0}{2025/04/10}{Enhancements in parsing speed.}
 
 \begin{document}
 \vspace*{-1cm}
@@ -306,12 +314,12 @@
 
 To install the package, copy the package file \macro{jsonparse.sty} into the working directory or into the \macro{texmf} directory. After the package has been installed, the \macro{jsonparse} package is loaded by calling \macro{\usepackage{jsonparse}} in the preamble of the document.
 
-The package does not load any dependencies. It can be used with PDFLaTeX, LuaLaTeX or XeLaTeX.
+The package does not load any dependencies. It can be used with PDFLaTeX, LuaLaTeX or XeLaTeX. It should also work with upTeX.
 
 \begin{macrodef}
 |debug|
 \end{macrodef}
-The package can be loaded with the option \macro{debug}. It will then output to the log file every instance of a string, a boolean (true or false) value, a null value, a number as well as the start and end of every object and the start and end of every array that is found while parsing the JSON string or JSON file. It will also show the relevant keys associated to the values including the pseudo key \macro{.} (or the string defined using the key \macro{separator/child}) that represents the complete JSON string.
+The package can be loaded with the option \macro{debug}. It will then output to the log file every instance of a string, a boolean (true or false) value, a null value, a number as well as the start and end of every object and the start and end of every array that is found while parsing the JSON string or JSON file. It will also show the relevant keys associated to the values. If the key \macro{skip structures} is not set (or not set to \macro{true}), objects and arrays will be shown as values to the respective keys, including the pseudo key \macro{.} (or the string defined using the key \macro{separator/child}) that represents the complete JSON string.
 
 Let us assume that the following JSON data is parsed:
 
@@ -326,7 +334,7 @@
   }
 \end{codeexample}
 
-This will then result to the following output to the log:
+This will then result in the following output to the log:
 
 \begin{codeexample}
   Parsing JSON ...
@@ -365,11 +373,11 @@
 
 In general, the package will read and store the JSON source and data as string, which means that all characters have category code 12 (``other''), except for spaces and (horizontal) tabs which have category code 10 (``space''). The \macro{\endlinechar} value is set to $-1$ which means that linefeeds and carriage returns are ignored by TeX. These settings are in line with the JSON specification of handling whitespace. Furthermore, if PDFLaTeX is used, the upper-half of the 8-bit range is set to ``active''. Additionally, JSON defines a small set of escape sequences and in order to be able to process these, the category code of the backslash is set to 0 (``escape'').
 
-During parsing, the package identifies JSON objects, arrays, strings, numbers, boolean values and null values from the JSON data. It stores all these values together with the relevant keys in a property list. Once the parsing process is done, every value can be retrieved from the property list by calling the relevant key. The package ignores whitespace in the JSON data. The JSON data should be an object or an array. In general, the package accepts any valid JSON data. If a key is defined multiple times, the latter definition will silently overwrite the former.
+During parsing, the package identifies JSON objects, arrays, strings, numbers, boolean values and null values from the JSON data. It stores all these values together with the relevant keys in a property list. Once the parsing process is done, every value can be retrieved from the property list by calling the relevant key. The package ignores whitespace in the JSON data. In general, the package accepts any valid JSON data. If a key is defined multiple times, the latter definition will silently overwrite the former.
 
 \section{Escaping and special treatment of the input}\label{sec:escaping}
 
-JSON strings cannot contain the two characters \macro{"} and \macro{\}. These two characters need to be escaped with a preceding backslash (\macro{\}).  This package therefore redefines locally the TeX control symbols \macro{\"}, \macro{\/}, \macro{\\}, \macro{\b}, \macro{\f}, \macro{\n}, \macro{\r}, \macro{\t} and \macro{\u}. These control symbols are prevented from expanding during parsing. For example, \macro{\"} is first defined as \macro{\exp_not:N \"} and only when typeset, \macro{\"} is expanded to \macro{"}, which ensures that strings are parsed properly.
+JSON strings cannot contain the two characters \macro{"} and \macro{\}. These two characters need to be escaped with a preceding backslash (\macro{\}). This package therefore redefines locally the TeX control symbols \macro{\"}, \macro{\/}, \macro{\\}, \macro{\b}, \macro{\f}, \macro{\n}, \macro{\r}, \macro{\t} and \macro{\u}. These control symbols are prevented from expanding during parsing. For example, \macro{\"} is first defined as \macro{\exp_not:N \"} and only when typeset, \macro{\"} is expanded to \macro{"}, which ensures that strings are parsed properly.
 
 Similarly, the control symbol \macro{\/} expands eventually to \macro{/} and \macro{\\} to \macro{\c_backslash_str} (i.\,e.\ a backslash with category code 12).
 
@@ -385,18 +393,18 @@
 Using the control sequence \macro{\x}, it is possible to nest JSON strings into each other. Used inside the \macro{\JSONParse} command, the control sequence takes two arguments delimited by curly braces. The first argument represents the name of the token variable that holds the parsed JSON data where the inserted JSON string should be taken from. The second argument sets the key that should be selected. The following example shows a simple use case:
 
 \begin{codeexamplecolumns}
-    \JSONParse{\myJSONdataA}{
-        { "a" : { "b" : "c" } }
-    }
+\JSONParse{\myJSONdataA}{
+  { "a" : { "b" : "c" } }
+}
 
-    \JSONParse{\myJSONdataB}{
-        { "d" : \x{myJSONdataA}{a} }
-    }
+\JSONParse{\myJSONdataB}{
+  { "d" : \x{myJSONdataA}{a} }
+}
 
-    \JSONParseValue{\myJSONdataB}{d.b}
+\JSONParseValue{\myJSONdataB}{d.b}
 \end{codeexamplecolumns}
 
-Note that the control sequence \macro{\x} is replaced by the value exactly. Therefore, if the value happens to be a string, the control sequence \macro{\x} should be placed between quotation marks (\macro{"}) in order for the resulting string to be valid JSON. The control sequence \macro{\x} is only available inside the \macro{\JSONParse} command, but not inside the \macro{\JSONParseFromFile} command.
+\warning Note that the control sequence \macro{\x} is replaced by the value exactly. Therefore, if the value happens to be a string, the control sequence \macro{\x} should be placed between quotation marks (\macro{"}) in order for the resulting string to be valid JSON. The control sequence \macro{\x} is only available inside the \macro{\JSONParse} command, but not inside the \macro{\JSONParseFromFile} command.
 
 \begin{macrodef}
 |escape|={all}
@@ -438,6 +446,8 @@
 
 The first optional argument can be used to pass options to the command that are then applied locally.
 
+\warning The command \macro{\JSONParse} takes the JSON string as verbatim argument which means that the command can't be used inside a macro argument. One consequence of this for example is that when using the \macro{beamer} document class, the command \macro{\JSONParse} can only be used inside the \macro{frame} environment if the \macro{fragile} option is set.
+
 \begin{macrodef}
 |\JSONParseFromFile|[<options>]{<token variable>}{<JSON file>}
 \end{macrodef}
@@ -454,9 +464,9 @@
 
 The first optional argument can be used to pass options to the command, such as \macro{escape} or \macro{rescan}, that are then applied locally. When the option \macro{rescan} is used, the token list is rescanned before it is typeset (which means that all category codes that may have been changed before are set to the default values). This is the default behavior. If rescanning is not desired, pass the option \macro{rescan=false} to the command.
 
-When a key is associated with an object or array, the whole object or array is output as JSON string. The special key \macro{.} (or the string defined using the key \macro{separator/child}) returns the whole JSON object (or the whole JSON array if the JSON data only consists of one array) as string where all characters (except for spaces and tabs) have category code 12 (``other'').
+When a key is associated with an object or array (and the key \macro{skip structures} is not set), the whole object or array is output as JSON string. Additionally, the special key \macro{.} (or the string defined using the key \macro{separator/child}) returns the whole JSON object (or the whole JSON array if the JSON data only consists of one array) as string where all characters (except for spaces and tabs) have category code 12 (``other'').
 
-The command \macro{\JSONParseValue} is not expandable and can therefore not be used as argument of certain other arguments where expansion is needed. In such cases, the expandable command \macro{\JSONParseExpandableValue} should be used.
+\warning The command \macro{\JSONParseValue} is not expandable and can therefore not be used as argument of certain other arguments where expansion is needed. In such cases, the expandable command \macro{\JSONParseExpandableValue} should be used.
 
 \begin{macrodef}[key:storein]
 |store in|={<token variable>}
@@ -493,6 +503,42 @@
 
 The command \macro{\JSONParseKeys} accepts as option the key \macro{store in} to get all top-level keys of a JSON object as JSON array and parse this array into a token variable. Note that the return value is stored as property list, not as string. The token variable to store the keys as array is created if it does not exist.
 
+As an example, let us assume that the following JSON data structure is parsed into the token variable \macro{\myJSONdata}:
+
+\begin{codeexample}
+{
+  "array" : [
+    {
+      "key_a" : "one" ,
+      "key_b" : "two"
+    } ,
+    {
+      "key_a" : "three" ,
+      "key_b" : "four"
+    }
+  ]
+}
+\end{codeexample}
+\JSONParse{\myJSONdata}{ { "array" : [ { "key_a" : "one" , "key_b" : "two" } , { "key_a" : "three" , "key_b" : "four" } ] } }
+
+We can then generate an array consisting of all keys of the object that is the first item of the array using \macro{\JSONParseKeys{\myJSONdata}{array[0]}}. We can also generate an array consisting of all top-level keys of the parsed JSON data using \macro{\JSONParseKeys{\myJSONdata}{.}} where \macro{.} is the pseudo key representing the complete parsed JSON string. Note that in addition to the key of an array, keys for each item of this array will be added to the array of keys:
+
+\begin{codeexamplecolumns}
+\JSONParseKeys{\myJSONdata}{array[0]}
+
+\JSONParseKeys{\myJSONdata}{.}
+\end{codeexamplecolumns}
+
+If we store such an array of keys in the token variable \macro{\myJSONkeys}, we can for example access the first item of this array of keys using \macro{\JSONParseValue{\myJSONkeys}{[0]}}:
+
+\begin{codeexamplecolumns}
+\JSONParseKeys[store in=\myJSONkeys]
+  {\myJSONdata}{.}
+\JSONParseValue{\myJSONkeys}{[0]}
+\end{codeexamplecolumns}
+
+Note that the underscores in the names of the keys can be printed without changing to math mode in the above example because they are stored as strings where all characters (except for spaces and tabs) have category code 12 (``other'').
+
 \begin{macrodef}
 |\JSONParseFilter|{<token variable>}{<token variable>}{<key>}
 \end{macrodef}
@@ -519,7 +565,7 @@
 \end{macrodef}
 The command \macro{\JSONParseArrayUse} is used to select all values from an array from a parsed JSON string or JSON file. The second argument takes the token variable that holds the parsed JSON data. The first argument takes the key to select the relevant entry from the parsed JSON data using JavaScript syntax. The third argument is optional and can be used to pass a subkey, i.\,e.\ a key that is used to select a value for every item. The last argument takes a string that is inserted between all values when they are typeset.
 
-For example, let us assume the following JSON data structure is parsed into the token variable \macro{\myJSONdata}:
+Let us again assume the following JSON data structure being parsed into the token variable \macro{\myJSONdata}:
 
 \begin{codeexample}
 {
@@ -535,7 +581,6 @@
   ]
 }
 \end{codeexample}
-\JSONParse{\myJSONdata}{ { "array" : [ { "key_a" : "one" , "key_b" : "two" } , { "key_a" : "three" , "key_b" : "four" } ] } }
 
 When using \macro{\JSONParseArrayUse{\myJSONdata}{array}[key_a]{, }}, `\JSONParseArrayUse{\myJSONdata}{array}[key_a]{, }' is then typeset to the document.
 
@@ -663,8 +708,10 @@
 
 Note that the underscores in the names of the keys can be printed without changing to math mode in the above example by switching off rescanning via \macro{rescan=false}. This is possible because all JSON data is stored as string where all characters (except for spaces and tabs) have category code 12 (``other'').
 
-The command \macro{\JSONParseArrayMapInline} accepts as option set in the optional argument the key \macro{store in} which takes a token variable into which the result of the mapped inline function should be stored. Refer to the relevant explanations to command \macro{\JSONParseArrayUse} \hyperlink{macro:arrayuse}{above} for more information. It is important to note that the inline function needs to be fully expandable. For example, it is not possible to use \macro{\JSONParseValue} in the code of the inline function while \macro{\JSONParseExpandableValue} is allowed. Note that the key \macro{store in} cannot be combined with nested use of \macro{\JSONParseArrayMapInline} because \macro{\JSONParseArrayMapInline} itself is not expandable.
+The command \macro{\JSONParseArrayMapInline} accepts as option set in the optional argument the key \macro{store in} which takes a token variable into which the result of the mapped inline function should be stored. Refer to the relevant explanations to command \macro{\JSONParseArrayUse} \hyperlink{macro:arrayuse}{above} for more information.
 
+\warning In order for the result to be stored in a token variable, the inline function needs to be fully expandable. For example, it is not possible to use the command \macro{\JSONParseValue} in the code of the inline function while using the command \macro{\JSONParseExpandableValue} is allowed. Note that the command \macro{\JSONParseArrayMapInline} itself is not expandable which means that nested use of this command prevents storing the result in a token variable.
+
 Storing the result of the mapped inline function can be helpful if JSON data should be reformatted for use in a plotting functions. An example for a use case with PGFplots is shown below. In this example, the parsed JSON string \macro{{ "data": [ [0,0], [1,-1], [2,1] ] }} was stored in the token variable \macro{\myJSONplotdata}.
 
 \JSONParse{\myJSONplotdata}{ { "data": [ [0,0], [1,-1], [2,1] ] } }
@@ -766,13 +813,21 @@
 This key can be set using \macro{\JSONParseSet}. It can also be set locally as option to the commands \macro{\JSONParse} and \macro{\JSONParseFromFile}. When set using \macro{\JSONParseSet}, this key only takes effect when set before parsing.
 
 \begin{macrodef}
-|check num|
-|check num|={<boolean>}
+|validate numbers|
+|validate numbers|={<boolean>}
 \end{macrodef}
-If set to \macro{false}, the key \macro{check num} omits an internal check of numerical expressions against the JSON specification for numbers. Turning off this feature can increase the parsing speed if many numbers are to be parsed. Checks are carried out per default.
+If set to \macro{false}, the key \macro{validate numbers} omits an internal validation of numerical expressions against the JSON specification for numbers. Turning off this feature can increase the parsing speed if many numbers are to be parsed. Validations are carried out per default.
 
 This key can be set using \macro{\JSONParseSet}. It can also be set locally as option to the commands \macro{\JSONParse} and \macro{\JSONParseFromFile}.
 
+\begin{macrodef}
+|skip structures|
+|skip structures|={<boolean>}
+\end{macrodef}
+If set or explicitly set to \macro{true}, the key \macro{skip structures} deactivates the storage of arrays and objects as values to the relative keys. Also, it omits storing of the whole JSON string as value with the pseudo key \macro{.} (or the string defined using the key \macro{separator/child}). Skipping structures is deactivated per default. Setting this key to true can speed up the parsing process and circumvent memory limitations.
+
+This key can be set using \macro{\JSONParseSet}. It can also be set locally as option to the commands \macro{\JSONParse} and \macro{\JSONParseFromFile}.
+
 \subsection{Keys affecting the typesetting}
 
 Some keys that change the typesetting behavior are explained in other parts of this documentation.

Modified: trunk/Master/texmf-dist/tex/latex/jsonparse/jsonparse.sty
===================================================================
--- trunk/Master/texmf-dist/tex/latex/jsonparse/jsonparse.sty	2025-04-09 19:58:08 UTC (rev 74896)
+++ trunk/Master/texmf-dist/tex/latex/jsonparse/jsonparse.sty	2025-04-09 19:58:17 UTC (rev 74897)
@@ -10,7 +10,7 @@
 %
 % This work has the LPPL maintenance status `maintained'.
 %
-\ProvidesExplPackage {jsonparse} {2025-03-30} {1.3.1}
+\ProvidesExplPackage {jsonparse} {2025-04-10} {1.4.0}
   {A handy way to parse, store and access JSON data from files or strings in LaTeX documents}
 
 \msg_new:nnn { jsonparse } { old-kernel } {
@@ -38,16 +38,34 @@
   #1
 }
 
-\msg_new:nnn { jsonparse } { parsing-error } {
+\msg_new:nnn { jsonparse } { parsing-error-generic } {
   \msg_error_text:n { jsonparse } \iow_newline:
   Could ~ not ~ parse ~ JSON. \iow_newline:
   Parsing ~ error ~ at ~ key ~ `#1` ~ with ~ value ~ `#2`.
 }
 
+\msg_new:nnn { jsonparse } { parsing-error-comma } {
+  \msg_error_text:n { jsonparse } \iow_newline:
+  Could ~ not ~ parse ~ JSON. \iow_newline:
+  Misplaced ~ comma.
+}
+
+\msg_new:nnn { jsonparse } { parsing-error-colon } {
+  \msg_error_text:n { jsonparse } \iow_newline:
+  Could ~ not ~ parse ~ JSON. \iow_newline:
+  Misplaced ~ colon ~ at ~ key ~ #1.
+}
+
+\msg_new:nnn { jsonparse } { parsing-error-key } {
+  \msg_error_text:n { jsonparse } \iow_newline:
+  Could ~ not ~ parse ~ JSON. \iow_newline:
+  Missing ~ key ~ in ~ object.
+}
+
 \msg_new:nnn { jsonparse } { nested-non-expandable } {
   \msg_error_text:n { jsonparse } \iow_newline:
   Non-expandable ~ commands ~ such ~ as ~ \token_to_str:N \JSONParseValue \c_space_tl
-  not ~ allowed ~ in ~ inline ~ function. \iow_newline:
+  not ~ allowed ~ in ~ stored ~ inline ~ function. \iow_newline:
   Use ~ \token_to_str:N \JSONParseExpandableValue \c_space_tl instead.
 }
 
@@ -61,16 +79,11 @@
   File ~ #1 ~ already ~ existing.
 }
 
-\msg_new:nnn { jsonparse } { escape-in-key } {
+\msg_new:nnn { jsonparse } { escape-char-unknown } {
   \msg_error_text:n { jsonparse } \iow_newline:
-  Invalid ~ escape ~ sequence ~ #1 ~ in ~ key.
+  Escape ~ character ~ #1 ~ unknown.
 }
 
-\msg_new:nnn { jsonparse } { escape-char-not-found } {
-  \msg_error_text:n { jsonparse } \iow_newline:
-  Escape ~ character ~ #1 ~ not ~ found.
-}
-
 \msg_new:nnn { jsonparse } { prop-undefined } {
   \msg_error_text:n { jsonparse } \iow_newline:
   Property ~ list ~ undefined: ~ #1.
@@ -81,7 +94,7 @@
   Control ~ sequence ~ undefined: ~ #1.
 }
 
-\msg_new:nnn { jsonparse } { unknown-key } {
+\msg_new:nnn { jsonparse } { key-unknown } {
   \msg_warning_text:n { jsonparse } \iow_newline:
   Ignoring ~ key: ~ #1. \iow_newline:
   The ~ key ~ is ~ either ~ unknown ~ or ~
@@ -109,7 +122,7 @@
 \str_new:N \l_jsonparse_current_prop_str
 
 \tl_new:N \l__jsonparse_externalize_file_name_tl
-\tl_new:N \l__jsonparse_externalize_file_data_tl
+\bool_new:N \l__jsonparse_externalize_bool
 
 \str_new:N \l__jsonparse_child_sep_str
 \str_new:N \l__jsonparse_array_sep_left_str
@@ -118,9 +131,9 @@
 \str_new:N \l__jsonparse_false_str
 \str_new:N \l__jsonparse_null_str
 \bool_new:N \l__jsonparse_zero_based_bool
-\bool_new:N \l__jsonparse_check_num_bool
+\bool_new:N \l__jsonparse_validate_numbers_bool
+\bool_new:N \l__jsonparse_skip_structures_bool
 \bool_new:N \l__jsonparse_rescan_bool
-\bool_new:N \l__jsonparse_externalize_bool
 
 \str_new:N \l__jsonparse_backspace_str
 \str_new:N \l__jsonparse_formfeed_str
@@ -131,11 +144,13 @@
 \tl_new:N \l__jsonparse_array_map_code_before_tl
 \tl_new:N \l__jsonparse_array_map_code_after_tl
 
-\tl_new:N \l__jsonparse_store_in_tl
-\bool_new:N \l__jsonparse_store_in_global_bool
+\tl_new:N \l__jsonparse_set_store_in_tl
+\bool_new:N \l__jsonparse_set_global_bool
 
 \clist_new:N \l__jsonparse_unused_keys_clist
 
+\str_new:N \l__jsonparse_escape_temp_str
+
 \clist_const:Nn \c__jsonparse_escape_tex_chars_clist {
   number_sign ,
   dollar_sign ,
@@ -146,7 +161,6 @@
   tilde
 }
 
-\str_new:N \l__jsonparse_escape_temp_str
 \clist_map_inline:Nn \c__jsonparse_escape_tex_chars_clist {
   \bool_new:c { l__jsonparse_escape_ #1 _bool }
 }
@@ -171,9 +185,12 @@
   zero-based                  .bool_set:N = \l__jsonparse_zero_based_bool ,
   zero-based                  .default:n  = { true } ,
   zero-based                  .initial:n  = { true } ,
-  check ~ num                 .bool_set:N = \l__jsonparse_check_num_bool ,
-  check ~ num                 .default:n  = { true } ,
-  check ~ num                 .initial:n  = { true } ,
+  validate ~ numbers          .bool_set:N = \l__jsonparse_validate_numbers_bool ,
+  validate ~ numbers          .default:n  = { true } ,
+  validate ~ numbers          .initial:n  = { true } ,
+  skip ~ structures           .bool_set:N = \l__jsonparse_skip_structures_bool ,
+  skip ~ structures           .default:n  = { true } ,
+  skip ~ structures           .initial:n  = { false } ,
   keyword                     .code:n     = { \keys_set:nn { jsonparse / parse / keyword } {#1} } ,
   keyword / true              .str_set:N  = \l__jsonparse_true_str ,
   keyword / true              .initial:n  = { true } ,
@@ -216,22 +233,22 @@
         } {
           \str_case:nnF {##1} {
             { hash } {
-              \bool_set_true:c { l__jsonparse_escape_number_sign_bool }
+              \bool_set_true:N \l__jsonparse_escape_number_sign_bool
             }
             { dollar } {
-              \bool_set_true:c { l__jsonparse_escape_dollar_sign_bool }
+              \bool_set_true:N \l__jsonparse_escape_dollar_sign_bool
             }
             { percent } {
-              \bool_set_true:c { l__jsonparse_escape_percent_sign_bool }
+              \bool_set_true:N \l__jsonparse_escape_percent_sign_bool
             }
             { circumflex } {
-              \bool_set_true:c { l__jsonparse_escape_circumflex_accent_bool }
+              \bool_set_true:N \l__jsonparse_escape_circumflex_accent_bool
             }
             { underscore } {
-              \bool_set_true:c { l__jsonparse_escape_low_line_bool }
+              \bool_set_true:N \l__jsonparse_escape_low_line_bool
             }
           } {
-            \msg_error:nno { jsonparse } { escape-char-not-found }
+            \msg_error:nno { jsonparse } { escape-char-unknown }
               {##1}
           }
         }
@@ -251,8 +268,8 @@
 }
 
 \keys_define:nn { jsonparse / set } {
-  store ~ in                  .tl_set:N   = \l__jsonparse_store_in_tl ,
-  global                      .bool_set:N = \l__jsonparse_store_in_global_bool ,
+  store ~ in                  .tl_set:N   = \l__jsonparse_set_store_in_tl ,
+  global                      .bool_set:N = \l__jsonparse_set_global_bool ,
   global                      .default:n  = { true } ,
   global                      .initial:n  = { false }
 }
@@ -259,7 +276,7 @@
 
 \cs_new_protected:Npn \__jsonparse_warning_unused_keys: {
   \clist_map_inline:Nn \l__jsonparse_unused_keys_clist {
-    \msg_warning:nne { jsonparse } { unknown-key } {
+    \msg_warning:nne { jsonparse } { key-unknown } {
       ##1
     }
   }
@@ -309,7 +326,7 @@
 \cs_generate_variant:Nn \tl_range:Nnn { Nne , Nen }
 \cs_generate_variant:Nn \tl_remove_once:Nn { NV }
 \cs_generate_variant:Nn \tl_replace_all:Nnn { Non , Noe }
-\cs_generate_variant:Nn \tl_replace_once:Nnn { Non }
+\cs_generate_variant:Nn \tl_replace_once:Nnn { Noe }
 \cs_generate_variant:Nn \tl_rescan:nn { no , ne }
 \cs_generate_variant:Nn \tl_set:Nn { Ne }
 \cs_generate_variant:Nn \tl_to_str:n { o , e }
@@ -370,6 +387,15 @@
 \tl_new:N \l__jsonparse_object_array_val_tl
 \tl_new:N \l__jsonparse_remainder_tl
 
+\bool_new:N \l__jsonparse_parse_array_bool
+\bool_new:N \l__jsonparse_parse_object_bool
+\bool_new:N \l__jsonparse_parse_key_bool
+\bool_new:N \l__jsonparse_parse_keys_first_bool
+
+\int_new:N \l__jsonparse_collect_object_array_level_int
+\tl_new:N \l__jsonparse_collect_object_array_store_tl
+\bool_new:N \l__jsonparse_collect_object_array_stop_bool
+
 \int_new:N \l__jsonparse_array_index_int
 \int_new:N \l__jsonparse_array_count_int
 \int_new:N \l__jsonparse_array_count_aux_int
@@ -388,9 +414,8 @@
 \str_new:N \l__jsonparse_array_map_function_str
 \int_new:N \g__jsonparse_array_map_inline_int
 
-\bool_new:N \l__jsonparse_prop_map_first_bool
+\tl_new:N \l__jsonparse_externalize_file_data_tl
 \bool_new:N \l__jsonparse_externalize_load_bool
-
 \iow_new:N \g__jsonparse_externalize_iow
 
 % ===
@@ -658,6 +683,80 @@
 
 % ===
 
+\cs_new:Npn \__jsonparse_collect_object_array:Nn #1#2 {
+  \tl_clear:N \l__jsonparse_collect_object_array_store_tl
+  \bool_set_false:N \l__jsonparse_collect_object_array_stop_bool
+  \int_zero:N \l__jsonparse_collect_object_array_level_int
+  \__jsonparse_collect_object_array:w #2 \q_stop
+  \tl_set_eq:NN #1 \l__jsonparse_collect_object_array_store_tl
+}
+\cs_generate_variant:Nn \__jsonparse_collect_object_array:Nn { No }
+
+\cs_new:Npn \__jsonparse_collect_object_array:w #1 \q_stop {
+  \bool_if:NF \l__jsonparse_collect_object_array_stop_bool {
+    \cs_if_exist_use:cTF { __jsonparse_collect_object_array_ \str_head:n {#1} :w } {
+      #1 \q_stop
+    } {
+      \tl_if_blank:eTF { \str_head:n {#1} } {
+        \__jsonparse_collect_object_array_space:w #1 \q_stop
+      } {
+        \__jsonparse_collect_object_array_other:w #1 \q_stop
+      }
+    }
+  }
+}
+
+\cs_new:cpn { __jsonparse_collect_object_array_ " :w } " #1 " #2 \q_stop {
+  \tl_put_right:Nn \l__jsonparse_collect_object_array_store_tl { " #1 " }
+  \__jsonparse_collect_object_array:w #2 \q_stop
+}
+
+\exp_last_unbraced:Nno
+  \cs_new:cpn { __jsonparse_collect_object_array_ \c_left_brace_str :w }
+    \c_left_brace_str #1 \q_stop {
+  \int_incr:N \l__jsonparse_collect_object_array_level_int
+  \tl_put_right:No \l__jsonparse_collect_object_array_store_tl { \c_left_brace_str }
+  \__jsonparse_collect_object_array:w #1 \q_stop
+}
+
+\exp_last_unbraced:Nno
+  \cs_new:cpn { __jsonparse_collect_object_array_ \c_right_brace_str :w }
+    \c_right_brace_str #1 \q_stop {
+  \int_decr:N \l__jsonparse_collect_object_array_level_int
+  \tl_put_right:No \l__jsonparse_collect_object_array_store_tl { \c_right_brace_str }
+  \int_if_zero:nT { \l__jsonparse_collect_object_array_level_int } {
+    \bool_gset_true:N \l__jsonparse_collect_object_array_stop_bool
+  }
+  \__jsonparse_collect_object_array:w #1 \q_stop
+}
+
+\cs_new:cpn { __jsonparse_collect_object_array_ [ :w } [ #1 \q_stop {
+  \int_incr:N \l__jsonparse_collect_object_array_level_int
+  \tl_put_right:Nn \l__jsonparse_collect_object_array_store_tl { [ }
+  \__jsonparse_collect_object_array:w #1 \q_stop
+}
+
+\cs_new:cpn { __jsonparse_collect_object_array_ ] :w } ] #1 \q_stop {
+  \int_decr:N \l__jsonparse_collect_object_array_level_int
+  \tl_put_right:Nn \l__jsonparse_collect_object_array_store_tl { ] }
+  \int_if_zero:nT { \l__jsonparse_collect_object_array_level_int } {
+    \bool_gset_true:N \l__jsonparse_collect_object_array_stop_bool
+  }
+  \__jsonparse_collect_object_array:w #1 \q_stop
+}
+
+\cs_new:Npn \__jsonparse_collect_object_array_space:w #1#2 \q_stop {
+  \tl_put_right:Nn \l__jsonparse_collect_object_array_store_tl { ~ }
+  \__jsonparse_collect_object_array:w #1#2 \q_stop
+}
+
+\cs_new:Npn \__jsonparse_collect_object_array_other:w #1#2 \q_stop {
+  \tl_put_right:Nn \l__jsonparse_collect_object_array_store_tl { #1 }
+  \__jsonparse_collect_object_array:w #2 \q_stop
+}
+
+% ===
+
 \cs_new_protected:cpn { __jsonparse_parse_ \c_left_brace_str :w } #1 \q_stop {
   \exp_last_unbraced:No
     \__jsonparse_parse_object_begin:w #1 \q_stop
@@ -678,6 +777,11 @@
     \__jsonparse_parse_array_end:w #1 \q_stop
 }
 
+\cs_new_protected:cpn { __jsonparse_parse_ , :w } #1 \q_stop {
+  \exp_last_unbraced:No
+    \__jsonparse_parse_delimiter:w #1 \q_stop
+}
+
 \cs_new_protected:cpn { __jsonparse_parse_ " :w } #1 \q_stop {
   \exp_last_unbraced:No
     \__jsonparse_parse_string_key:w #1 \q_stop
@@ -700,14 +804,17 @@
 
 \exp_last_unbraced:NNo \cs_new_protected:Npn \__jsonparse_parse_object_begin:w \c_left_brace_str #1 \q_stop {
   \__jsonparse_array_key_set:
+  % object begin
+  \bool_if:NT \l__jsonparse_debug_mode_bool {
+    \msg_log:nnn { jsonparse } { debug-info } {
+      (obj ~ begin)
+    }
+  }
   \group_begin:
+    \bool_set_true:N \l__jsonparse_parse_object_bool
+    \bool_set_false:N \l__jsonparse_parse_array_bool
+    \bool_set_false:N \l__jsonparse_parse_key_bool
     \tl_set:Nn \l__jsonparse_remainder_tl {#1}
-    % object begin
-    \bool_if:NT \l__jsonparse_debug_mode_bool {
-      \msg_log:nnn { jsonparse } { debug-info } {
-        (obj ~ begin)
-      }
-    }
     \tl_if_empty:NTF \l__jsonparse_key_tl {
       \tl_set_eq:NN \l__jsonparse_object_array_key_tl \l__jsonparse_child_sep_str
     } {
@@ -714,26 +821,27 @@
       \tl_set_eq:NN \l__jsonparse_object_array_key_tl \l__jsonparse_key_tl
       \tl_set:Ne \l__jsonparse_prefix_tl { \l__jsonparse_key_tl \l__jsonparse_child_sep_str }
     }
-    \tl_set:Nn \l__jsonparse_object_array_val_tl { \c_left_brace_str #1 }
+    \bool_if:NF \l__jsonparse_skip_structures_bool {
+      \__jsonparse_collect_object_array:No \l__jsonparse_object_array_val_tl { \c_left_brace_str #1 }
+    }
     \__jsonparse_parse_remainder:
 }
 
 \exp_last_unbraced:NNo \cs_new_protected:Npn \__jsonparse_parse_object_end:w \c_right_brace_str #1 \q_stop {
-    \tl_set:Ne \l__jsonparse_object_array_val_tl {
-      \tl_range:Nne \l__jsonparse_object_array_val_tl { 1 } {
-        \int_eval:n {
-          -1 * \tl_count:n {#1} - 1
+    \bool_if:NF \l__jsonparse_parse_key_bool {
+      \msg_error:nn { jsonparse } { parsing-error-key }
+    }
+    \bool_if:NF \l__jsonparse_skip_structures_bool {
+      \prop_gput:Nee \g_jsonparse_entries_prop
+        { \l__jsonparse_object_array_key_tl } { \l__jsonparse_object_array_val_tl }
+      \bool_if:NT \l__jsonparse_debug_mode_bool {
+        \msg_log:nne { jsonparse } { debug-info } {
+          (key) ~ \str_use:N \l__jsonparse_object_array_key_tl : \iow_newline:
+          \iow_char:N \  \iow_char:N \  (obj) ~ \str_use:N \l__jsonparse_object_array_val_tl
         }
       }
     }
-    \prop_gput:Nee \g_jsonparse_entries_prop
-      { \l__jsonparse_object_array_key_tl } { \l__jsonparse_object_array_val_tl }
-    \bool_if:NT \l__jsonparse_debug_mode_bool {
-      \msg_log:nne { jsonparse } { debug-info } {
-        (key) ~ \str_use:N \l__jsonparse_object_array_key_tl : \iow_newline:
-        \iow_char:N \  \iow_char:N \  (obj) ~ \str_use:N \l__jsonparse_object_array_val_tl
-      }
-    }
+    \bool_set_false:N \l__jsonparse_parse_object_bool
   \group_end:
   % object end
   \bool_if:NT \l__jsonparse_debug_mode_bool {
@@ -747,14 +855,16 @@
 
 \cs_new_protected:Npn \__jsonparse_parse_array_begin:w [ #1 \q_stop {
   \__jsonparse_array_key_set:
+  % array begin
+  \bool_if:NT \l__jsonparse_debug_mode_bool {
+    \msg_log:nnn { jsonparse } { debug-info } {
+      (arr ~ begin)
+    }
+  }
   \group_begin:
+    \bool_set_false:N \l__jsonparse_parse_object_bool
+    \bool_set_true:N \l__jsonparse_parse_array_bool
     \tl_set:Nn \l__jsonparse_remainder_tl {#1}
-    % array begin
-    \bool_if:NT \l__jsonparse_debug_mode_bool {
-      \msg_log:nnn { jsonparse } { debug-info } {
-        (arr ~ begin)
-      }
-    }
     \int_zero:N \l__jsonparse_array_index_int
     \bool_if:NT \l__jsonparse_zero_based_bool {
       \int_decr:N \l__jsonparse_array_index_int
@@ -764,27 +874,25 @@
     } {
       \tl_set_eq:NN \l__jsonparse_object_array_key_tl \l__jsonparse_key_tl
     }
-    \tl_set:Nn \l__jsonparse_object_array_val_tl { [ #1 }
     \tl_set:Ne \l__jsonparse_prefix_tl { \l__jsonparse_key_tl \l__jsonparse_array_sep_left_str }
+    \bool_if:NF \l__jsonparse_skip_structures_bool {
+      \__jsonparse_collect_object_array:Nn \l__jsonparse_object_array_val_tl { [ #1 }
+    }
     \__jsonparse_parse_remainder:
 }
 
 \cs_new_protected:Npn \__jsonparse_parse_array_end:w ] #1 \q_stop {
-    \tl_set:Ne \l__jsonparse_object_array_val_tl {
-      \tl_range:Nne \l__jsonparse_object_array_val_tl { 1 } {
-        \int_eval:n {
-          -1 * \tl_count:n {#1} - 1
+    \bool_if:NF \l__jsonparse_skip_structures_bool {
+      \prop_gput:Nee \g_jsonparse_entries_prop
+        { \l__jsonparse_object_array_key_tl } { \l__jsonparse_object_array_val_tl }
+      \bool_if:NT \l__jsonparse_debug_mode_bool {
+        \msg_log:nne { jsonparse } { debug-info } {
+          (key) ~ \str_use:N \l__jsonparse_object_array_key_tl : \iow_newline:
+          \iow_char:N \  \iow_char:N \  (arr) ~ \str_use:N \l__jsonparse_object_array_val_tl
         }
       }
     }
-    \prop_gput:Nee \g_jsonparse_entries_prop
-      { \l__jsonparse_object_array_key_tl } { \l__jsonparse_object_array_val_tl }
-    \bool_if:NT \l__jsonparse_debug_mode_bool {
-      \msg_log:nne { jsonparse } { debug-info } {
-        (key) ~ \str_use:N \l__jsonparse_object_array_key_tl : \iow_newline:
-        \iow_char:N \  \iow_char:N \  (arr) ~ \str_use:N \l__jsonparse_object_array_val_tl
-      }
-    }
+    \bool_set_false:N \l__jsonparse_parse_array_bool
   \group_end:
   % array end
   \bool_if:NT \l__jsonparse_debug_mode_bool {
@@ -796,13 +904,50 @@
   \__jsonparse_parse_remainder:
 }
 
+\cs_new_protected:Npn \__jsonparse_parse_delimiter:w , #1 \q_stop {
+  \bool_lazy_or:nnF { \l__jsonparse_parse_object_bool } { \l__jsonparse_parse_array_bool } {
+    % comma not in object or array
+    \msg_error:nn { jsonparse } { parsing-error-comma }
+  }
+  \bool_lazy_and:nnT { \l__jsonparse_parse_object_bool } { \bool_not_p:n { \l__jsonparse_parse_key_bool } } {
+    % object item without key
+    \msg_error:nn { jsonparse } { parsing-error-key }
+  }
+  \bool_set_false:N \l__jsonparse_parse_key_bool
+  \tl_set:Nn \l__jsonparse_remainder_tl {#1}
+  \__jsonparse_parse_remainder:
+}
+
 \cs_new_protected:Npn \__jsonparse_parse_string_key:w " #1 " #2 \q_stop {
   \__jsonparse_array_key_set:
-  \tl_set:Ne \l__jsonparse_remainder_tl { \tl_trim_spaces:n {#2} }
+  \tl_set:Nn \l__jsonparse_temp_tl { #2 \s__jsonparse_split }
+  \tl_replace_once:Nnn \l__jsonparse_temp_tl { , } { \s__jsonparse_split , }
+  \tl_replace_once:Nnn \l__jsonparse_temp_tl { ] } { \s__jsonparse_split ] }
+  \tl_replace_once:Noe \l__jsonparse_temp_tl { \c_right_brace_str } { \s__jsonparse_split \c_right_brace_str }
+  \tl_replace_once:Noe \l__jsonparse_temp_tl { \c_colon_str } { \s__jsonparse_split \c_colon_str }
+  \tl_put_left:Nn \l__jsonparse_temp_tl { #1 " }
+  \exp_last_unbraced:No
+    \__jsonparse_parse_string_key_aux:w \l__jsonparse_temp_tl \q_stop
+}
+
+\cs_new_protected:Npn \__jsonparse_parse_string_key_aux:w #1 " #2 \s__jsonparse_split #3 \q_stop {
+  \tl_set:No \l__jsonparse_remainder_tl {#3}
+  \tl_remove_all:Nn \l__jsonparse_remainder_tl { \s__jsonparse_split }
+  \tl_if_blank:nF {#2} {
+    % non-whitespace between delimiters found
+    \msg_error:nnoo { jsonparse } { parsing-error-generic }
+      { \l__jsonparse_key_tl } {#1}
+  }
   % key or string?
   \tl_if_head_eq_charcode:oNTF { \l__jsonparse_remainder_tl } : {
+    \bool_if:NF \l__jsonparse_parse_object_bool {
+      % colon not in object
+      \msg_error:nno { jsonparse } { parsing-error-colon }
+        { \l__jsonparse_key_tl }
+    }
     \tl_remove_once:NV \l__jsonparse_remainder_tl \c_colon_str
     \tl_set:Ne \l__jsonparse_key_tl { \l__jsonparse_prefix_tl #1 }
+    \bool_set_true:N \l__jsonparse_parse_key_bool
   } {
     \tl_set:Nn \l__jsonparse_val_tl {#1}
     \prop_gput:Nee \g_jsonparse_entries_prop
@@ -823,7 +968,7 @@
   \tl_set:Nn \l__jsonparse_temp_tl { #1 \s__jsonparse_split }
   \tl_replace_once:Nnn \l__jsonparse_temp_tl { , } { \s__jsonparse_split , }
   \tl_replace_once:Nnn \l__jsonparse_temp_tl { ] } { \s__jsonparse_split ] }
-  \tl_replace_once:Non \l__jsonparse_temp_tl { \c_right_brace_str } { \s__jsonparse_split \c_right_brace_str }
+  \tl_replace_once:Noe \l__jsonparse_temp_tl { \c_right_brace_str } { \s__jsonparse_split \c_right_brace_str }
   \exp_last_unbraced:No
     \__jsonparse_parse_other_aux:w \l__jsonparse_temp_tl \q_stop
 }
@@ -833,7 +978,7 @@
   \tl_remove_all:Nn \l__jsonparse_remainder_tl { \s__jsonparse_split }
   \tl_set:Ne \l__jsonparse_temp_tl { \tl_trim_spaces:n {#1} }
   \cs_if_exist_use:cF { __jsonparse_parse_ \str_casefold:o { \l__jsonparse_temp_tl } : } {
-    \bool_if:NTF \l__jsonparse_check_num_bool {
+    \bool_if:NTF \l__jsonparse_validate_numbers_bool {
       \jsonparse_if_num:VTF \l__jsonparse_temp_tl {
         \tl_set:No \l__jsonparse_val_tl { \l__jsonparse_temp_tl }
         \prop_gput:Nee \g_jsonparse_entries_prop
@@ -847,7 +992,7 @@
         }
       } {
         % not a valid JSON number
-        \msg_error:nnoo { jsonparse } { parsing-error }
+        \msg_error:nnoo { jsonparse } { parsing-error-generic }
           { \l__jsonparse_key_tl } {#1}
       }
     } {
@@ -907,9 +1052,6 @@
 
 \cs_new_protected:Npn \__jsonparse_parse_remainder: {
   \tl_set:Ne \l__jsonparse_remainder_tl { \tl_trim_spaces:e { \l__jsonparse_remainder_tl } }
-  \tl_if_head_eq_charcode:oNT { \l__jsonparse_remainder_tl } , {
-    \tl_remove_once:Nn \l__jsonparse_remainder_tl { , }
-  }
   \tl_if_empty:NF \l__jsonparse_remainder_tl {
     \jsonparse_parse:o { \l__jsonparse_remainder_tl }
   }
@@ -1139,10 +1281,15 @@
 \cs_generate_variant:Nn \jsonparse_unicode_convert_surrogate_pair:nn { ee }
 
 \cs_new:Npn \__jsonparse_unicode_char:NNNNN #1#2#3#4#5 {
-  \__jsonparse_unicode_char_aux:nNNNN { } #1#2#3#4
   \cs_if_eq:NNTF #5 \u {
-    \__jsonparse_unicode_char_aux:nNNNN { " #1#2#3#4 }
+    \jsonparse_unicode_if_high_surrogate:eTF { " \str_uppercase:n {#1#2#3#4} } {
+      \__jsonparse_unicode_char_aux:nNNNN { " #1#2#3#4 }
+    } {
+      \__jsonparse_unicode_char_aux:nNNNN { } #1#2#3#4
+      \__jsonparse_unicode_char:NNNNN
+    }
   } {
+    \__jsonparse_unicode_char_aux:nNNNN { } #1#2#3#4
     #5
   }
 }
@@ -1149,9 +1296,7 @@
 
 \cs_new:Npn \__jsonparse_unicode_char_aux:nNNNN #1#2#3#4#5 {
   \tl_if_empty:nTF {#1} {
-    \jsonparse_unicode_if_high_surrogate:eF { " \str_uppercase:n {#2#3#4#5} } {
-      \codepoint_generate:en { " \str_uppercase:n {#2#3#4#5} } { 12 }
-    }
+    \codepoint_generate:en { " \str_uppercase:n {#2#3#4#5} } { 12 }
   } {
     \jsonparse_unicode_if_low_surrogate:eTF { " \str_uppercase:n {#2#3#4#5} } {
       \codepoint_generate:nn {
@@ -1280,15 +1425,15 @@
     \keys_set_known:nonN { jsonparse / set } { \l__jsonparse_unused_keys_clist }
       { jsonparse / set }\l__jsonparse_unused_keys_clist
     \__jsonparse_warning_unused_keys:
-    \tl_if_empty:NF \l__jsonparse_store_in_tl {
-      \exp_last_unbraced:No \tl_if_exist:NF \l__jsonparse_store_in_tl {
-        \exp_last_unbraced:No \tl_new:N \l__jsonparse_store_in_tl
+    \tl_if_empty:NF \l__jsonparse_set_store_in_tl {
+      \exp_last_unbraced:No \tl_if_exist:NF \l__jsonparse_set_store_in_tl {
+        \exp_last_unbraced:No \tl_new:N \l__jsonparse_set_store_in_tl
       }
     }
     \exp_args:NNoee
   \group_end:
-  \__jsonparse_parse_value_aux:nnnnn { \l__jsonparse_store_in_tl }
-    { \bool_if_p:N \l__jsonparse_store_in_global_bool } { \bool_if_p:N \l__jsonparse_rescan_bool }
+  \__jsonparse_parse_value_aux:nnnnn { \l__jsonparse_set_store_in_tl }
+    { \bool_if_p:N \l__jsonparse_set_global_bool } { \bool_if_p:N \l__jsonparse_rescan_bool }
     {#2} {#3}
 }
 
@@ -1308,8 +1453,8 @@
 
 \cs_new_protected:Npn \__jsonparse_parse_keys:nn #1#2 {
   \tl_if_in:nVF {#1} \l__jsonparse_child_sep_str {
-    \bool_if:NTF \l__jsonparse_prop_map_first_bool {
-      \bool_set_false:N \l__jsonparse_prop_map_first_bool
+    \bool_if:NTF \l__jsonparse_parse_keys_first_bool {
+      \bool_set_false:N \l__jsonparse_parse_keys_first_bool
     } {
       \tl_put_right:Nn \l__jsonparse_keys_array_tl { , }
     }
@@ -1319,6 +1464,7 @@
 
 \cs_new_protected:Npn \jsonparse_parse_keys:NN #1#2 {
   \tl_set:Nn \l__jsonparse_keys_array_tl { [ }
+  \bool_set_true:N \l__jsonparse_parse_keys_first_bool
   \prop_map_function:NN #1 \__jsonparse_parse_keys:nn
   \tl_put_right:Nn \l__jsonparse_keys_array_tl { ] }
   \tl_set_eq:NN #2 \l__jsonparse_keys_array_tl
@@ -1344,17 +1490,16 @@
     \__jsonparse_warning_unused_keys:
     \prop_set_eq:NN \l__jsonparse_temp_copy_prop #2
     \jsonparse_filter:Nn \l__jsonparse_temp_copy_prop {#3}
-    \bool_set_true:N \l__jsonparse_prop_map_first_bool
     \jsonparse_parse_keys:NN \l__jsonparse_temp_prop \l__jsonparse_keys_tl
-    \tl_if_empty:NF \l__jsonparse_store_in_tl {
-      \exp_last_unbraced:No \tl_if_exist:NF \l__jsonparse_store_in_tl {
-        \exp_last_unbraced:No \tl_new:N \l__jsonparse_store_in_tl
+    \tl_if_empty:NF \l__jsonparse_set_store_in_tl {
+      \exp_last_unbraced:No \tl_if_exist:NF \l__jsonparse_set_store_in_tl {
+        \exp_last_unbraced:No \tl_new:N \l__jsonparse_set_store_in_tl
       }
     }
     \exp_args:NNoeV
   \group_end:
-  \__jsonparse_parse_keys_aux:nnn { \l__jsonparse_store_in_tl }
-    { \bool_if_p:N \l__jsonparse_store_in_global_bool } \l__jsonparse_keys_tl
+  \__jsonparse_parse_keys_aux:nnn { \l__jsonparse_set_store_in_tl }
+    { \bool_if_p:N \l__jsonparse_set_global_bool } \l__jsonparse_keys_tl
 }
 
 % backward compatibility
@@ -1410,15 +1555,15 @@
     \prop_set_eq:NN \l__jsonparse_temp_copy_prop #2
     \jsonparse_filter:Nn \l__jsonparse_temp_copy_prop {#3}
     \jsonparse_array_count:NN \l__jsonparse_temp_copy_prop \l__jsonparse_array_count_int
-    \tl_if_empty:NF \l__jsonparse_store_in_tl {
-      \exp_last_unbraced:No \tl_if_exist:NF \l__jsonparse_store_in_tl {
-        \exp_last_unbraced:No \tl_new:N \l__jsonparse_store_in_tl
+    \tl_if_empty:NF \l__jsonparse_set_store_in_tl {
+      \exp_last_unbraced:No \tl_if_exist:NF \l__jsonparse_set_store_in_tl {
+        \exp_last_unbraced:No \tl_new:N \l__jsonparse_set_store_in_tl
       }
     }
     \exp_args:NNoeV
   \group_end:
-  \__jsonparse_array_count_aux:nnn { \l__jsonparse_store_in_tl }
-    { \bool_if_p:N \l__jsonparse_store_in_global_bool } \l__jsonparse_array_count_int
+  \__jsonparse_array_count_aux:nnn { \l__jsonparse_set_store_in_tl }
+    { \bool_if_p:N \l__jsonparse_set_global_bool } \l__jsonparse_array_count_int
 }
 
 % backward compatibility
@@ -1487,15 +1632,15 @@
     \str_set:Ne \l__jsonparse_array_use_key_str {#4}
     \int_step_function:nN \l__jsonparse_array_count_int
       \__jsonparse_array_use:n
-    \tl_if_empty:NF \l__jsonparse_store_in_tl {
-      \exp_last_unbraced:No \tl_if_exist:NF \l__jsonparse_store_in_tl {
-        \exp_last_unbraced:No \tl_new:N \l__jsonparse_store_in_tl
+    \tl_if_empty:NF \l__jsonparse_set_store_in_tl {
+      \exp_last_unbraced:No \tl_if_exist:NF \l__jsonparse_set_store_in_tl {
+        \exp_last_unbraced:No \tl_new:N \l__jsonparse_set_store_in_tl
       }
     }
     \exp_args:NNoe
   \group_end:
-  \__jsonparse_array_use_aux:nnn { \l__jsonparse_store_in_tl }
-    { \bool_if_p:N \l__jsonparse_store_in_global_bool } {#5}
+  \__jsonparse_array_use_aux:nnn { \l__jsonparse_set_store_in_tl }
+    { \bool_if_p:N \l__jsonparse_set_global_bool } {#5}
 }
 
 % backward compatibility
@@ -1698,17 +1843,17 @@
     \bool_if:NT \l__jsonparse_zero_based_bool {
       \int_decr:N \l__jsonparse_array_count_int
     }
-    \tl_if_empty:NF \l__jsonparse_store_in_tl {
+    \tl_if_empty:NF \l__jsonparse_set_store_in_tl {
       \__jsonparse_warning_function_expandable:V \g__jsonparse_array_map_inline_int
-      \exp_last_unbraced:No \tl_if_exist:NF \l__jsonparse_store_in_tl {
-        \exp_last_unbraced:No \tl_new:N \l__jsonparse_store_in_tl
+      \exp_last_unbraced:No \tl_if_exist:NF \l__jsonparse_set_store_in_tl {
+        \exp_last_unbraced:No \tl_new:N \l__jsonparse_set_store_in_tl
       }
     }
     \exp_args:NNoeeVV
   \group_end:
   \__jsonparse_array_map_inline_aux:nnnnn
-    { \l__jsonparse_store_in_tl }
-    { \bool_if_p:N \l__jsonparse_store_in_global_bool }
+    { \l__jsonparse_set_store_in_tl }
+    { \bool_if_p:N \l__jsonparse_set_global_bool }
     { \bool_if_p:N \l__jsonparse_zero_based_bool }
     \l__jsonparse_array_count_int
     \g__jsonparse_array_map_inline_int



More information about the tex-live-commits mailing list.