purescript-contrib/purescript-argonaut-codecs

Instances for NonEmpty Array and NonEmpty List

dgendill opened this issue · 4 comments

Would it make sense to have instances for NonEmpty Array and NonEmpty List?

instance encodeJsonNonEmptyArray :: (EncodeJson a) => EncodeJson (NonEmpty Array a) where
  encodeJson (NonEmpty h t) = encodeJson $ cons h t

instance encodeJsonNonEmptyList :: (EncodeJson a) => EncodeJson (NonEmpty List a) where
  encodeJson (NonEmpty h t) = encodeJson $ cons h (toUnfoldable t)
garyb commented

I think something like that would make sense 👍

Maybe?

instance encodeJsonNonEmptyFoldable :: (EncodeJson a, Foldable f) => EncodeJson (NonEmpty f a) where 
  encodeJson (NonEmpty h t) = 
    encodeJson $ StrMap.fromFoldable [ Tuple "head" h, Tuple "tail" $ foldMap Arr.singleton t ]

And

instance decodeJsonNonEmptyUnfoldable :: (DecodeJson a, Unfoldable f) => DecodeJson (NonEmpty f a) where 
  decodeJson js = decodeJson >=> \obj -> do
    h <- obj .? "head"
    tl <- map (unfoldr (Arr.uncons >>> map (\{head, tail} -> Tuple head tail)) $ obj .? "tail" 
    pure $ h :| tl 

I like the idea of having a way to encode/decode any Foldable/Unfoldable. Would having a specific instance for Array and List, along with the more generalized form work? My thought is that a NonEmpty Array or a NonEmpty List should be turned into a JSON array, so that the PureScript types more closely resemble the underlying javascript types.

For my use case, I'd like to model the configuration object to create a RTCIceServer and the urls property can be either a string or an array of strings (presumably not an empty array). In modeling that, I thought this structure would be good.

data ServerType
  = STUN { urls :: NonEmpty Array String }
  | TURN { urls :: NonEmpty Array String, credentialType :: Maybe String, credential :: Maybe String, username :: Maybe String }

instance serverTypeEncodeJson :: EncodeJson ServerType where
  encodeJson (STUN s) = jsonSingletonObject "urls" (encodeJson s.urls)
  encodeJson (TURN t) = (
    "urls" := t.urls
    ~> "credentialType" := t.credentialType
    ~> "credential" := t.credential
    ~> "username" := t.username
    ~> jsonEmptyObject
  )

So then I could encode the ServerType into Json and pass the Json into the FFI to create an RTCIceServer.