purescript-argonaut: Decode arbitrary key-value json

641 Views Asked by At

Is there a way to decode arbitrary json (e.g: We don't know the keys at compile time)?

For example, I need to parse the following json:

{ 
    "Foo": [
        "Value 1",
        "Value 2"
    ],
    "Bar": [
        "Bar Value 1"
    ],
    "Baz": []
}

where the names and number of keys are not known at compile time and may change per GET request. The goal is basically to decode this into a Map String (Array String) type

Is there a way to do this using purescript-argonaut?

2

There are 2 best solutions below

0
Fyodor Soikin On BEST ANSWER

You can totally write your own by first parsing the string into Json via jsonParser, and then examining the resulting data structure with the various combinators provided by Argonaut.

But the quickest and simplest way, I think, is to parse it into Foreign.Object (Array String) first, and then convert to whatever your need, like Map String (Array String):

import Data.Argonaut (decodeJson, jsonParser)
import Data.Either (Either)
import Data.Map as Map
import Foreign.Object as F

decodeAsMap :: String -> Either _ (Map.Map String (Array String))
decodeAsMap str = do
    json <- jsonParser str
    obj <- decodeJson json
    pure $ Map.fromFoldable $ (F.toUnfoldable obj :: Array _)
0
rnons On

The Map instance of EncodeJSON will generate an array of tuple, you can manually construct a Map and see the encoded json.

let v = Map.fromFoldable [ Tuple "Foo" ["Value1", "Value2"] ]
traceM $ encodeJson v

Output should be [ [ 'Foo', [ 'Value1', 'Value2' ] ] ].

To do the reverse, you need to transform you object to an array of tuple, Object.entries can help you.

An example

// Main.js
var obj = {
  foo: ["a", "b"],
  bar: ["c", "d"]
};

exports.tuples = Object.entries(obj);

exports.jsonString = JSON.stringify(exports.tuples);
-- Main.purs
module Main where

import Prelude

import Data.Argonaut.Core (Json)
import Data.Argonaut.Decode (decodeJson)
import Data.Argonaut.Parser (jsonParser)
import Data.Either (Either)
import Data.Map (Map)
import Debug.Trace (traceM)
import Effect (Effect)
import Effect.Console (log)

foreign import tuples :: Json
foreign import jsonString :: String

main :: Effect Unit
main = do
  let
    a = (decodeJson tuples) :: Either String (Map String (Array String))
    b = (decodeJson =<< jsonParser jsonString) :: Either String (Map String (Array String))
  traceM a
  traceM b
  traceM $ a == b