1

I'm working on a JSON data encoder in Haskell! (to be more specific, I am trying to port Jo into Haskell!). I've gotten a lot of it working, but I'm running into a little wrinkle. I'll try to be concise with my question here, I've tried to strip away as much unneeded context as possible.

Goal: Construct a Value given a String.

These strings come from the command line: Users enter in key/value pairs in the form <key>=<value>. After splitting them apart I am left in a String that is the value data of unknown type.

Example cases:

let s = "someString" -- use the `String` constructor
let s = "1234"       -- use the `Number` constructor
let s = "True"       -- use the `Bool` constructor 

Question: How might I infer that the contents of s is a String vs a Number, Bool, etc?

This is the relevant type + constructors for the Aeson Value type (edited for brevity).

data Value = Object Object
           | Array Array
           | String Text
           | Number Scientific
           | Bool Bool
           | Null
jbakerj
  • 55
  • 1
  • 6
  • 6
    You need (to buy, beg, steal, borrow, or write) a *parser*. – n. m. could be an AI Jul 04 '22 at 18:32
  • 1
    Did you mean `"\"someString\""`? – Bergi Jul 04 '22 at 18:34
  • @n.1.8e9-where's-my-sharem. that's what I feared / expected, just not really sure where to get started. Would you do it with vanilla Haskell? A specific package? I'm _very_ new to the language and ecosystem, just need to get pointed in the right direction :-) – jbakerj Jul 04 '22 at 18:48
  • @Bergi No I don't believe so. cases will not contain any quotes as part of the string itself – jbakerj Jul 04 '22 at 18:49
  • 1
    @jbakerj Then how do you know it's supposed to be a string and not a `Null` or `Bool` value? – Bergi Jul 04 '22 at 18:50
  • 1
    There is a zillion way to write a parser in Haskell, you need to know at least one. You can start [here](https://stackoverflow.com/questions/20660782/writing-a-parser-from-scratch-in-haskell). – n. m. could be an AI Jul 04 '22 at 18:55
  • 1
    A json string actually [must be quoted](https://www.json.org/json-en.html),. – n. m. could be an AI Jul 04 '22 at 18:56
  • 1
    Ah, I can clarify. The values I would like to infer at not yet encoded in JSON. After I construct a `Value` I pass that into the `Aeson` package for JSON encoding, `Aeson` handles quotes/no quotes, pretty printing with curly braces, etc. This parsing issue is a little upstream of that: these strings I want to parse are collected from the command line and I need to figure out which `Value` constructor to use so `Aeson` encode them properly. At no point am I reading JSON. I will update my question to make that more clear. Thanks for pointing me in the right direction though! – jbakerj Jul 04 '22 at 19:11
  • So you have a *language* (not json, some other language) you are trying to parse (and feed the parsed data to a json encoder, but this is not really relevant). This makes things somewhat clearer. Now an interesting question is how you define a string in that language. – n. m. could be an AI Jul 04 '22 at 19:26
  • You need to define (at least on paper) a *grammar* that defines rules like "the string `True` maps to the Boolean value `True`, not the `String` value `"True"`. A parser is just a function that *implements* the rules defined in the grammar. – chepner Jul 04 '22 at 19:45

1 Answers1

2

Since you're already using the aeson package, you could use decode. This works because Value is also a ByteString instance:

Prelude Data.Aeson> decode "\"someString\"" :: Maybe Value
Just (String "someString")
Prelude Data.Aeson> decode "1234" :: Maybe Value
Just (Number 1234.0)
Prelude Data.Aeson> decode "true" :: Maybe Value
Just (Bool True)

Notice (as n. 1.8e9-where's-my-share m. points out in the comments) that strings must be quoted.

What you could do, then, is to take your unknown value and first surround it with quotes and attempt to parse it. Then try to parse it again without surrounding quotes.

You now have two Maybe Value values. Pick the first Just value (and be prepared to deal with the Nothing case).

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 2
    You want to do those things in the opposite order, surely? If you first try parsing it as a string, you will always succeed. Instead, first try parsing it as something else, and fall back to a string if that fails. – amalloy Jul 04 '22 at 20:55
  • 1
    @amalloy That sounds right. It's one of many reasons that I prefer writing automated tests - I don't trust my own reasoning abilities. – Mark Seemann Jul 04 '22 at 21:11
  • This was a great suggestion. Oddly the only type that it 'broke' was string, which will return `Nothing`. In that case I just passed the string into the `toJSON` value encoder which can already handle strings and things seem to be working well. ``` toValue :: BL.ByteString -> Value toValue x = case decode x of Just x -> x Nothing -> toJSON $ lazyToText x ``` – jbakerj Jul 04 '22 at 21:14