Working with JSON using Typed Racket

72 views
Skip to first unread message

e...@disroot.org

unread,
Mar 29, 2020, 8:39:56 AM3/29/20
to racket...@googlegroups.com
Hi everyone,

Recently I've been experimenting with Typed Racket and trying to gradually type my code base.
One of the functions that I need to write is to extract a list of strings from a JSON object, if it has following form:

{
"Type": "DTHidden",
"Data": { "Type": "CDUsers",
"Data": ["datum1", "datum2"] }
}

The way I used to structure it in Racket is to have a function `is-cdusers?` with the contract `jsexpr? -> boolean?`, which would check that the JSON object has the right shape; and a separate function `get-cdusers-data` with the contract `is-cdusers? -> listof string?`.

However, after playing a bit with Typed Racket I decided that it was necessary, according to my understanding of occurrence typing, to have a single function `get-cdusers-data` with the type `JSExpr -> (U False (Listof String))`.
In order to get it to work I ended up writing a long chain of conditionals:


(: get-cdusers-data (-> JSExpr (U False (Listof Any))))
(define (get-cdusers-data js)
(if (and (hash? js)
(equal? DTHidden (hash-ref js 'Type #f)))
(let ([js (hash-ref-def js 'Data [ann #hasheq() JSExpr])])
(if (and (hash? js)
(equal? CdUsers (hash-ref js 'Type #f)))
(let ([data (hash-ref js 'Data)])
(if (hash? data)
(let ([x (hash-ref js 'Data #f)])
(and (list? x) x))
#f))
#f))
#f))

Needless to say, this is a bit impractical and error-prone to write.
Does anyone know if there is a better approach to this?

From my experience with typed languages I would get that the most principle approach is to have an algebraic data type that represents all the underlying data structures, something like

type reply = ... | CDUsers of string list | ...

and then have a single function to converts a JSExpr into that data type.

I was hoping to avoid that, because I do enjoy working with the JSExpr type directly in Racket.

Does anyone have advice/experience with problems like this?

Best wishes,
-Ed

Wesley Bitomski

unread,
Mar 30, 2020, 8:40:38 PM3/30/20
to Racket Users
Hello Ed,

I generally get some mileage out of using custom predicates (those that are made with define-predicate) and pattern matching when tearing into JSON structures in Typed Racket. Occurrence typing seems to work with if, cond and assert, along with match's predicate matcher (? form).

So, something like this I think could less tedious to produce, and does exactly what your example does:

(: jsexpr->cd-users-data (JSExpr -> (Option (Listof String))))
(define (jsexpr->cd-users-data js)
 
(define-predicate names-list? (Listof String))
 
(match js
   
[(hash-table ['Type "DTHidden"]
                 ['
Data (hash-table ['Type "CDUsers"]
                                    ['
Data (? names-list? users)])])
     users
]
   
[_ #false]))

I hope this is what you were looking for.

e...@disroot.org

unread,
Mar 31, 2020, 3:23:07 PM3/31/20
to Wesley Bitomski, Racket Users
Thanks Wesley and Matthias! Very helpful advice.
--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/f85a4a4d-0d0a-47e3-909b-2b378def368a%40googlegroups.com.


Phil Nguyen

unread,
Apr 8, 2020, 4:03:20 AM4/8/20
to Racket Users
For parsing JSON in Typed Racket, you can check out this library I made: https://github.com/philnguyen/json-type-provider .

You can declare structs and field names that match the underlying data, and get back a well-typed parser. Don't let the name "type provider" fool you though, it's nothing smart like F#'s type provider (there's no inference from examples yet), although it's intended to eventually be so.

For your example, you could equivalently do something like below. If you omit the default values, then instead of silently failing with `#f`, it'll give you error messages pinpointing the location in the JSON stream.

#lang typed/racket/base

(require racket/match
         json
-comb)

(define-json-types
 
[DTHidden ([Type : String #:default [#f : #f]]
           
[Data : CDUsers #:default [#f : #f]])]
 
[CDUsers ([Type : String #:default [#f : #f]]
           
[Data : (Listof String) #:default [#f : #f]])])

(: read-cdusers-data : Input-Port (U #f (Listof String)))
(define (read-cdusers-data in)
 
(match (read-DTHidden in)
   
[(DTHidden "DTHidden" (CDUsers "CDUsers" ans)) ans]
   
[_ #f]))

(define example
 
"{
 \"Type\": \"DTHidden\",
 \"Data\": { \"Type\": \"CDUsers\",
 \"Data\": [\"datum1\", \"datum2\"] }
 }}"
)

(read-cdusers-data (open-input-string example))

To unsubscribe from this group and stop receiving emails from it, send an email to racket...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages