garyb / purescript-codec-argonaut

Bi-directional JSON codecs for argonaut
MIT License
39 stars 16 forks source link

Add strMap #55

Closed thomashoneyman closed 2 years ago

thomashoneyman commented 2 years ago

I regularly need to encode a Map String a (or, equivalently, Map k a where k can be printed / parsed as a string). There's no easy way to do this right now besides converting to a Foreign.Object, but it's nice to skip the ceremony if I can simply convert the keys to/from strings as needed but retain a map structure.

thomashoneyman commented 2 years ago

I also frequently want to work with a map that has some type as the key that can be serialized to a string, so it should be represented in JSON as an object, which would perhaps work out to something like this:

strMap :: forall k a. Ord k => String -> (String -> Maybe k) -> (k -> String) -> JsonCodec a -> JsonCodec (Map.Map k a)
strMap ty parse print valueCodec = Codec.basicCodec decode encode
  where
  encode :: Map k a -> Json
  encode m = Codec.encode CA.jobject $ Object.runST do
    obj <- Object.ST.new
    forWithIndex_ m \k v -> Object.ST.poke (print k) (Codec.encode valueCodec v) obj
    pure obj

  decode :: Json -> Either CA.JsonDecodeError (Map k a)
  decode json = do
    array :: Array _ <- Object.toUnfoldable <$> Codec.decode CA.jobject json
    parsed <- array # traverse \(Tuple k v) -> do
      key <- note (CA.Named k (CA.TypeMismatch ty)) (parse k)
      val <- lmap (CA.AtKey k) (Codec.decode valueCodec v)
      pure $ Tuple key val
    pure $ Map.fromFoldable parsed

unless there's a reasonable way to convert a JsonCodec (Map String a) to a JsonCodec (Map k a)?

garyb commented 2 years ago

I thought I was onto something for this that would be more flexible and handle both cases here as well as foreign objects.

The general idea being:

But I'm not quite satisfied with it yet. If I can't get it figured out tonight I'll just merge this 🙂

thomashoneyman commented 2 years ago

No rush! I'm using this strMap implementation in a codebase for now, but I'll tear it out if we come up with something nicer.