bug#77762: [PATCH] web: Add JSON module.

2025-04-12 Thread Thompson, David
Attached is a patch that adds a new (web json) module. Some may
remember that I submitted a patch back in 2015 (time flies, eh?) for
an (ice-9 json) module that never made it in. Well, 10 years is a long
time and Guile still doesn't have a built-in JSON module. Third party
libraries like guile-json and guile-sjson are available, the latter
being an adaptation of my original patch and the former remaining the
go-to library used by larger Guile projects like Guix. There's also
SRFI-180 (which sounds like a cool surfing trick!) which was published
in 2020 but the API is, in my opinion, overly complicated due to
generators and other things.  Anyway, JSON continues to be *the* data
interchange format of the web and Guile really ought to have a simple
API that can read/write JSON to/from a port using only Scheme data
types that have read syntax (i.e. no hash tables like guile-json).
This minimal, practical API is what my patch provides.  I've tried my
best to make it as efficient as possible.

I've settled on the following JSON<->Scheme data type mapping which is
nearly identical to SRFI-180 with the exception of object keys:

- true and false are #t and #f
- null is the symbol 'null
- numbers are either exact integers (fixnums and bignums) or inexact
reals (flonums, NaNs and infinities excluded)
- strings are strings
- arrays are vectors
- objects are association lists with string keys (SRFI-180 chose
symbols but JSON uses strings so strings feel the most honest)

Thanks in advance for the review,

- Dave
From 104b57e2a7b4ca47096cb524aff688b0ada49f94 Mon Sep 17 00:00:00 2001
From: David Thompson 
Date: Sat, 12 Apr 2025 08:27:35 -0400
Subject: [PATCH] web: Add JSON module.

* module/web/json.scm: New file.
* am/bootstrap.am (SOURCES): Add it.
* test-suite/tests/json.test: New file.
* test-suite/Makefile.am (SCM_TESTS): Add it.
* doc/ref/web.texi ("JSON"): New subsection.
---
 am/bootstrap.am|   3 +-
 doc/ref/web.texi   |  93 +++
 module/web/json.scm| 308 +
 test-suite/Makefile.am |   1 +
 test-suite/tests/json.test | 154 +++
 5 files changed, 558 insertions(+), 1 deletion(-)
 create mode 100644 module/web/json.scm
 create mode 100644 test-suite/tests/json.test

diff --git a/am/bootstrap.am b/am/bootstrap.am
index 96023d83d..6806fda5d 100644
--- a/am/bootstrap.am
+++ b/am/bootstrap.am
@@ -425,7 +425,8 @@ SOURCES =	\
   web/response.scm\
   web/server.scm\
   web/server/http.scm\
-  web/uri.scm
+  web/uri.scm	\
+  web/json.scm
 
 ELISP_SOURCES =	\
   language/elisp/boot.el
diff --git a/doc/ref/web.texi b/doc/ref/web.texi
index 607c855b6..53ce14820 100644
--- a/doc/ref/web.texi
+++ b/doc/ref/web.texi
@@ -40,6 +40,7 @@ back.
 * Transfer Codings::HTTP Transfer Codings.
 * Requests::HTTP requests.
 * Responses::   HTTP responses.
+* JSON::The JavaScript Object Notation.
 * Web Client::  Accessing web resources over HTTP.
 * Web Server::  Serving HTTP to the internet.
 * Web Examples::How to use this thing.
@@ -1448,6 +1449,98 @@ Return @code{#t} if @var{type}, a symbol as returned by
 @end deffn
 
 
+@node JSON
+@subsection JSON
+
+@cindex json
+@cindex (web json)
+
+@example
+(use-modules (web json))
+@end example
+
+JavaScript Object Notation (JSON) is the most common data interchange
+format on the web.  It is ubiquitous in HTTP APIs and has found its way
+into many other domains beyond the web, as well.  The @code{(web json)}
+module makes it possible to convert a subset of Scheme data types to
+JSON text and vice versa.  For example, the JSON document:
+
+@example
+@verbatim
+{
+  "name": "Eva Luator",
+  "age": 24,
+  "schemer": true,
+  "hobbies": [
+"hacking",
+"cycling",
+"surfing"
+  ]
+}
+@end verbatim
+@end example
+
+can be represented with the following Scheme expression:
+
+@example
+@verbatim
+'(("name" . "Eva Luator")
+  ("age" . 24)
+  ("schemer" . #t)
+  ("hobbies" . #("hacking" "cycling" "surfing")))
+@end verbatim
+@end example
+
+Strings, exact integers, inexact reals (excluding NaNs and infinities),
+@code{#t}, @code{#f}, the symbol @code{null}, vectors, and association
+lists may be serialized as JSON.  Association lists serialize as JSON
+objects and vectors serialize as JSON arrays.  The keys of association
+lists @emph{must} be strings.
+
+@deffn {Scheme Procedure} read-json [port]
+
+Parse a JSON-encoded value from @var{port} and return its Scheme
+representation.  If @var{port} is unspecified, the current input port is
+used.
+
+@example
+@verbatim
+(call-with-input-string "[true,false,null,42,\"foo\"]" read-json)
+;; => #(#t #f null 42 "foo")
+
+(call-with-input-string "{\"foo\":1,\"bar\":2}" read-json)
+;; => (("foo" . 1) ("bar" . 2))
+@end verbatim
+@end example
+
+@end deffn
+
+@deftp {Exception Type} &json-read-error
+An exception type denot

bug#77762: [PATCH] web: Add JSON module.

2025-04-12 Thread Thompson, David
On Sat, Apr 12, 2025 at 12:01 PM Tomas Volf <~@wolfsden.cz> wrote:
>
> > - null is the symbol 'null
>
> Out of curiosity, what are your thoughts about using #nil instead?

My original patch from 10 years ago did this. Mark Weaver then
explained to me that #nil was added specifically for the purpose of
supporting Emacs Lisp on the Guile VM and shouldn't be used in Scheme
code. The symbol 'null works well because there is no symbol type in
JSON so there's no ambiguity.

- Dave





bug#77762: [PATCH] web: Add JSON module.

2025-04-12 Thread Arun Isaac


Hi David,

Thanks for the patch. I'm just trying to understand: Why do we need a
JSON module in guile itself? Isn't guile-json, the way it is as an
external library, good enough?

> API that can read/write JSON to/from a port using only Scheme data
> types that have read syntax (i.e. no hash tables like guile-json).

guile-json does not use hash tables. guile-json uses association lists.
I believe the decision to use hash tables was reversed many years ago.

Cheers!
Arun





bug#77762: [PATCH] web: Add JSON module.

2025-04-12 Thread Tomas Volf
> - null is the symbol 'null

Out of curiosity, what are your thoughts about using #nil instead?

Tomas

-- 
There are only two hard things in Computer Science:
cache invalidation, naming things and off-by-one errors.





bug#77762: [PATCH] web: Add JSON module.

2025-04-12 Thread Thompson, David
Hi Arun,

On Sat, Apr 12, 2025 at 9:39 PM Arun Isaac  wrote:
>
> Thanks for the patch. I'm just trying to understand: Why do we need a
> JSON module in guile itself? Isn't guile-json, the way it is as an
> external library, good enough?

I mean, Guile doesn't *need* to include anything, but JSON is one of
the most common serialization formats around. Most language
implementations ship with a JSON library.  Guile already has a suite
of web modules and so this fits right in with those.  Over the years
I've noticed many users, especially new users, asking why Guile
doesn't ship with JSON support. I don't think you should have to
install an external library for something so common. In my own
projects I prefer to just check a JSON module into the source tree so
I don't have to add an additional dependency. guile-json is a good
library with a bunch of bells and whistles, but I think most people
just want basic read/write procedures and would be happy to take them
for granted with their Guile installation.

> > API that can read/write JSON to/from a port using only Scheme data
> > types that have read syntax (i.e. no hash tables like guile-json).
>
> guile-json does not use hash tables. guile-json uses association lists.
> I believe the decision to use hash tables was reversed many years ago.

Heh, shows how long it's been since I last used guile-json. Glad
that's changed. :)

- Dave