Skip to content

Instantly share code, notes, and snippets.

@soraismus
Created March 25, 2019 05:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save soraismus/bfa243ddb797c6c3d4387d8c1bf072c7 to your computer and use it in GitHub Desktop.
Save soraismus/bfa243ddb797c6c3d4387d8c1bf072c7 to your computer and use it in GitHub Desktop.
Attempt to generalize or expand upon generic decoding of json
module FlexDecode.Class
( class FlexDecodeJson
, class FlexGDecodeJson
, flexDecodeJson
, flexGDecodeJson
) where
import Prelude
import Data.Argonaut.Core (Json, toObject)
import Data.Argonaut.Decode (class DecodeJson, decodeJson)
import Data.Either (Either(Left, Right))
import Data.Maybe (Maybe(Just, Nothing))
import Data.Symbol (class IsSymbol, SProxy(SProxy), reflectSymbol)
import Foreign.Object as FO
import Prim.Row as Row
import Prim.RowList as RL
import Record as Record
import Type.Data.RowList (RLProxy(RLProxy))
import Type.Proxy (Proxy(Proxy))
import Type.Row as R
class FlexGDecodeJson (row :: # Type) (list :: RL.RowList) | list -> row where
flexGDecodeJson :: FO.Object Json -> RLProxy list -> Either String (Record row)
instance flexGDecodeJsonCons
:: ( DecodeJson value
, FlexGDecodeJson rowTail tail
, IsSymbol field
, Row.Cons field (Maybe value) rowTail row
, Row.Lacks field rowTail
)
=> FlexGDecodeJson row (RL.Cons field (Maybe value) tail) where
flexGDecodeJson object _ = do
let sProxy = SProxy :: SProxy field
let fieldName = reflectSymbol sProxy
rest <- flexGDecodeJson object (RLProxy :: RLProxy tail)
case FO.lookup fieldName object of
Just jsonVal -> do
val <- decodeJson jsonVal
Right $ Record.insert sProxy (Just val) rest
Nothing ->
Right $ Record.insert sProxy Nothing rest
instance flexGDecodeJsonNil :: FlexGDecodeJson () RL.Nil where
flexGDecodeJson _ _ = Right {}
class FlexDecodeJson a where
flexDecodeJson :: Json -> Either String a
instance flexDecodeJsonRecord
:: ( FlexGDecodeJson row list
, RL.RowToList row list
)
=> FlexDecodeJson (Record row) where
flexDecodeJson json =
case toObject json of
Just object -> flexGDecodeJson object (RLProxy :: RLProxy list)
Nothing -> Left "Could not convert JSON to object"
decodeJsonLeniently
:: forall list0 list1 row0 row1 row2
. R.RowToList row0 list0
=> R.RowToList row1 list1
=> R.Union row0 row1 row2
=> GDecodeJson row0 list0
=> FlexGDecodeJson row1 list1
-> RProxy row1
-> Json
-> Either String (Record row2)
decodeJsonLeniently _ json =
case toObject json of
Nothing -> Left "Could not convert JSON to object"
Just object ->
Right
$ Record.merge
(gDecodeJson object (RLProxy :: RLProxy list0))
(flexGDecodeJson object (RLProxy :: RLProxy list1))
class MatchCases
(rl :: R.RowList)
(rw :: # Type)
| rl -> rw
instance matchCasesCons
:: ( MatchCases rl rw'
, R.Cons sym a rw' rw
, TypeEquals k (Json -> Either String a)
)
=> MatchCases (R.Cons sym k rl) rw
instance matchCasesNil
:: MatchCases R.Nil () b
class OnMatch (l :: R.RowList) (r0 :: # Type) (r1 :: # Type) | r1 -> r0 l where
onMatch :: RLProxy l -> Record r0 -> FO.Object Json -> Either String (Record r1)
instance onMatch_nil :: OnMatch RL.Nil () () where
onMatch _ _ _ = Right {}
instance onMatch_cons
:: ( R.RowToList row list
, MatchCases list decoderRow
, R.RowToList decoderRow decoderList
, RL.Cons field value list' list
, RL.Cons field decoderValue decoderList' decoderList
, IsSymbol field
, OnMatch decoderList' decoderRow list'
)
=> OnMatch (RL.Cons field decoderValue decoderList') decoderRow row
where
onMatch _ decoderRecord object = do
let
sProxy = SProxy :: SProxy field
fieldName = reflectSymbol sProxy
decoder = Record.get sProxy decoderRecord
rest <- onMatch (RLProxy :: RLProxy decoderList') decoderRecord object
case FO.lookup fieldName object of
Just jsonVal ->
val <- decoder jsonVal
Right $ Record.insert sProxy val rest
Nothing ->
Left $ "JSON was missing expected field: " <> fieldName
decodeWith
:: forall decoderRow decoderList list0 list1 row0 row1 row2
. R.RowToList row0 list0
=> R.RowToList decoderRow decoderList
-- => MatchCases list0 decoderRow
=> R.Union row0 row1 row2
=> R.RowToList row1 list1
=> GDecodeJson row1 list1
=> OnMatch decoderList decoderRow row0
=> Record decoderRow
-> Json
-> Either String (Record row2)
decodeWith decoderRecord = case toObject _ of
Just object -> Right $ _decodeWith decoderRecord object
Nothing -> Left "Could not convert JSON to object"
_decodeWith
:: forall decoderRow decoderList list0 list1 row0 row1 row2
. R.RowToList row0 list0
=> R.RowToList decoderRow decoderList
-- => MatchCases list0 decoderRow
=> R.Union row0 row1 row2
=> R.RowToList row1 list1
=> GDecodeJson row1 list1
=> OnMatch decoderList decoderRow row0
=> Record decoderRow
-> FO.Object Json
-> Either String (Record row2)
_decodeWith decoderRecord object = Record.merge record0 record1
where
record0 :: Record row0
record1 = onMatch (RLProxy :: RLProxy decoderList) decoderRecord object
record1 :: Record row1
record1 = gDecodeJson object (RLProxy :: RLProxy list1)
-- ===========================================================================
class MatchCasesLeniently
(rl :: R.RowList)
(rw :: # Type)
| rl -> rw
instance matchCasesLenientlyCons
:: ( MatchCases rl rw'
, R.Cons sym (Maybe a) rw' rw
, TypeEquals k (Json -> Either String (Maybe a))
)
=> MatchCasesLeniently (R.Cons sym k rl) rw
instance matchCasesLenientlyNil
:: MatchCases R.Nil () b
class OnLenientMatch (l :: R.RowList) (r0 :: # Type) (r1 :: # Type) | r1 -> r0 l where
onLenientMatch :: RLProxy l -> Record r0 -> FO.Object Json -> Either String (Record r1)
instance onLenientMatch_nil :: OnLenientMatch RL.Nil () () where
onLenientMatch _ _ _ = Right {}
instance onLenientMatch_cons
:: ( R.RowToList row list
, MatchCases list decoderRow
, R.RowToList decoderRow decoderList
, RL.Cons field (Maybe value) list' list
, RL.Cons field decoderValue decoderList' decoderList
, IsSymbol field
, OnLenientMatch decoderList' decoderRow list'
)
=> OnLenientMatch (RL.Cons field decoderValue decoderList') decoderRow row
where
onLenientMatch _ decoderRecord object = do
let
sProxy = SProxy :: SProxy field
fieldName = reflectSymbol sProxy
decoder = Record.get sProxy decoderRecord
rest <- onLenientMatch (RLProxy :: RLProxy decoderList') decoderRecord object
case FO.lookup fieldName object of
Just jsonVal ->
val <- decoder jsonVal
Right $ Record.insert sProxy val rest
Nothing ->
Right $ Record.insert sProxy Nothing rest
decodeLenientlyWith
:: forall decoderRow decoderList list0 list1 row0 row1 row2
. R.RowToList row0 list0
=> R.RowToList decoderRow decoderList
-- => MatchCasesLeniently list0 decoderRow
=> R.Union row0 row1 row2
=> R.RowToList row1 list1
=> GDecodeJson row1 list1
=> OnMatch decoderList decoderRow row0
=> Record decoderRow
-> Json
-> Either String (Record row2)
decodeLenientlyWith decoderRecord = case toObject _ of
Just object -> Right $ _decodeLenientlyWith decoderRecord object
Nothing -> Left "Could not convert JSON to object"
_decodeLenientlyWith
:: forall decoderRow decoderList list0 list1 row0 row1 row2
. R.RowToList row0 list0
=> R.RowToList decoderRow decoderList
-- => MatchCasesLeniently list0 decoderRow
=> R.Union row0 row1 row2
=> R.RowToList row1 list1
=> GDecodeJson row1 list1
=> OnMatch decoderList decoderRow row0
=> Record decoderRow
-> FO.Object Json
-> Either String (Record row2)
_decodeLenientlyWith decoderRecord object = Record.merge record0 record1
where
record0 :: Record row0
record1 = onLenientMatch (RLProxy :: RLProxy decoderList) decoderRecord object
record1 :: Record row1
record1 = gDecodeJson object (RLProxy :: RLProxy list1)
@soraismus
Copy link
Author

module Data.Argonaut.FlexDecode where

import Prelude

import Control.Alternative (class Alternative, empty)
import Data.Argonaut.Core (Json, toObject)
import Data.Argonaut.Decode.Class
  ( class DecodeJson
  , class GDecodeJson
  , decodeJson
  , gDecodeJson
  )
import Data.Either (Either(Left, Right))
import Data.Maybe (Maybe(Just, Nothing))
import Data.Status.Class (class Status, report, reportError)
import Data.Symbol (class IsSymbol, SProxy(SProxy), reflectSymbol)
import Foreign.Object (Object, lookup)
import Record (delete, get, insert, merge, union)
import Type.Data.RowList (RLProxy(RLProxy)) -- Argonaut dependency
import Type.Equality (class TypeEquals, to)
import Type.Proxy (Proxy(Proxy))
import Type.Row
  ( kind RowList
  , Cons
  , Nil
  , RProxy(RProxy)
  , class Cons
  , class Lacks
  , class Nub
  , class RowToList
  , class Union
  )
import Type.Row as Row

_decodeJson :: forall a. DecodeJson a => Proxy a -> Json -> Either String a
_decodeJson _ = decodeJson

reportJson
  :: forall f r
   . Status f
  => (Object Json -> f (Record r))
  -> Json
  -> f (Record r)
reportJson f json =
  case toObject json of
    Just object -> f object
    Nothing -> reportError "Could not convert JSON to object"

reportObject
  :: forall f l0 r0 r1
   . GDecodeJson r0 l0
  => RowToList r0 l0
  => Status f
  => (Record r0 -> Record r1)
  -> Object Json
  -> RLProxy l0
  -> f (Record r1)
reportObject f object rlProxy =
  case gDecodeJson object rlProxy of
    Left errorStr -> reportError errorStr
    Right record -> report $ f record

class FlexGDecodeJson f (row :: # Type) (list :: RowList) | list -> row where
  flexGDecodeJson :: Object Json -> RLProxy list -> f (Record row)

instance flexGDecodeJsonCons
  :: ( Alternative f
     , Cons field (f value) rowTail row
     , DecodeJson value
     , FlexGDecodeJson g rowTail tail
     , IsSymbol field
     , Lacks field rowTail
     , Monad g
     , Status g
     )
  => FlexGDecodeJson g row (Cons field (f value) tail) where
  flexGDecodeJson object _ = do
    let sProxy = SProxy :: SProxy field
    let fieldName = reflectSymbol sProxy
    rest <- flexGDecodeJson object (RLProxy :: RLProxy tail)
    case lookup fieldName object of
      Just jsonVal ->
        case decodeJson jsonVal of
          Left errorStr -> reportError errorStr
          Right val     -> report $ insert sProxy (pure val) rest
      Nothing ->
        report $ insert sProxy empty rest

instance flexGDecodeJsonNil
  :: Status f
  => FlexGDecodeJson f () Nil where
  flexGDecodeJson _ _ = report {}

class FlexDecodeJson f a where
  flexDecodeJson' :: Json -> f a

instance flexDecodeJsonRecord
  :: ( FlexGDecodeJson f row list
     , RowToList row list
     , Status f
     )
  => FlexDecodeJson f (Record row) where
  flexDecodeJson' = reportJson $ flip flexGDecodeJson (RLProxy :: RLProxy list)

flexDecodeJson
  :: forall f list0 list1 list2 row0 row1 row2
   . FlexGDecodeJson f row0 list0
  => GDecodeJson row1 list1
  => Monad f
  => Nub row2 row2
  => RowToList row0 list0
  => RowToList row1 list1
  => RowToList row2 list2
  => Status f
  => Union row0 row1 row2
  => RProxy row0
  -> Json
  -> f (Record row2)
flexDecodeJson _ = reportJson go
  where
  go object = do
    record0 <- flexGDecodeJson object (RLProxy :: RLProxy list0)
    reportObject (merge record0) object (RLProxy :: RLProxy list1)

flexDecodeJson_
  :: forall f list0 list1 list2 row0 row1 row2
   . FlexGDecodeJson f row0 list0
  => GDecodeJson row1 list1
  => Monad f
  => RowToList row0 list0
  => RowToList row1 list1
  => RowToList row2 list2
  => Status f
  => Union row0 row1 row2
  => RProxy row0
  -> Json
  -> f (Record row2)
flexDecodeJson_ _ = reportJson go
  where
  go object = do
    record0 <- flexGDecodeJson object (RLProxy :: RLProxy list0)
    reportObject (union record0) object (RLProxy :: RLProxy list1)

class DecodeCases
  (f :: Type -> Type)
  (list :: RowList)
  (row :: # Type)
  | list -> row

instance decodeCasesCons
  :: ( Cons field value row' row
     , DecodeCases f list' row'
     , TypeEquals decoderValue (Json -> f value)
     )
  => DecodeCases f (Cons field decoderValue list') row

instance decodeCasesNil :: DecodeCases f Nil ()

class
  ( DecodeCases f l1 r0
  , RowToList r1 l1
  ) <=
  DecodeJsonWith
    (f :: Type -> Type)
    (l1 :: RowList)
    (r1 :: # Type)
    (r0 :: # Type)
    | r0 -> r1 l1 where
    decodeJsonWith'
      :: Record r1
      -> Json
      -> f (Record r0)

instance decodeJsonWithDecodeJsonWith_
  :: ( DecodeCases f l1 r0
     , DecodeJsonWith_ f l1 r1 l0 r0
     , RowToList r0 l0
     , RowToList r1 l1
     , Status f
     )
  => DecodeJsonWith f l1 r1 r0
  where
  decodeJsonWith' decoderRecord =
    reportJson $
      __decodeJsonWith
        (RLProxy :: RLProxy l0)
        (RLProxy :: RLProxy l1)
        decoderRecord

class DecodeJsonWith_
  (f :: Type -> Type)
  (l1 :: RowList)
  (r1 :: # Type)
  (l0 :: RowList)
  (r0 :: # Type)
  | l1 -> r1 l0 r0 where
  __decodeJsonWith
    :: RLProxy l0
    -> RLProxy l1
    -> Record r1
    -> Object Json
    -> f (Record r0)

instance __decodeJsonWithNil
  :: Status f
  => DecodeJsonWith_ f Nil () Nil () where
  __decodeJsonWith _ _ _ _ = report {}

instance __decodeJsonWithCons
  :: ( Cons field value row' row
     , Cons field decoderValue decoderRow' decoderRow
     , DecodeCases f decoderList row
     , DecodeCases f decoderList' row'
     , DecodeJsonWith_ f decoderList' decoderRow' list' row'
     , IsSymbol field
     , Lacks field row'
     , Lacks field decoderRow'
     , Monad f
     , RowToList row list
     , RowToList row' list'
     , RowToList decoderRow decoderList
     , RowToList decoderRow' decoderList'
     , Status f
     , TypeEquals decoderValue (Json -> f value)
     )
  => DecodeJsonWith_
       f
       (Cons field decoderValue decoderList')
       decoderRow
       (Cons field value list')
       row
  where
  __decodeJsonWith _ _ decoderRecord object = do
    let
      sProxy :: SProxy field
      sProxy = SProxy

      fieldName :: String
      fieldName = reflectSymbol sProxy

      decoder :: Json -> f value
      decoder = to $ get sProxy decoderRecord

    rest <-
      __decodeJsonWith
        (RLProxy :: RLProxy list')
        (RLProxy :: RLProxy decoderList')
        (delete sProxy decoderRecord)
        object

    case lookup fieldName object of
      Just jsonVal -> do
        val <- decoder jsonVal
        report $ insert sProxy val rest
      Nothing ->
        reportError $ "JSON was missing expected field: " <> fieldName

decodeJsonWith
  :: forall decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . DecodeJsonWith_ f decoderList decoderRow list0 row0
  => GDecodeJson row1 list1
  => Monad f
  => Nub row2 row2
  => RowToList row1 list1
  => RowToList row2 list2
  => RowToList decoderRow decoderList
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
decodeJsonWith decoderRecord = reportJson go
  where
  go :: Object Json -> f (Record row2)
  go object = do
    record0 <-
      __decodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
    reportObject (merge record0) object (RLProxy :: RLProxy list1)

decodeJsonWith_
  :: forall decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . DecodeJsonWith_ f decoderList decoderRow list0 row0
  => GDecodeJson row1 list1
  => Monad f
  => RowToList row1 list1
  => RowToList row2 list2
  => RowToList decoderRow decoderList
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
decodeJsonWith_ decoderRecord = reportJson go
  where
  go :: Object Json -> f (Record row2)
  go object = do
    record0 <-
      __decodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
    reportObject (union record0) object (RLProxy :: RLProxy list1)

class FlexDecodeCases
  (f :: Type -> Type)
  (list :: RowList)
  (row :: # Type)
  | list -> row

instance flexDecodeCasesCons
  :: ( Cons field (f value) row' row
     , FlexDecodeCases g list' row'
     , TypeEquals decoderValue (Json -> g (f value))
     )
  => FlexDecodeCases g (Cons field decoderValue list') row

instance flexDecodeCasesNil :: FlexDecodeCases f Nil ()

class
  ( FlexDecodeCases f l1 r0
  , RowToList r1 l1
  ) <=
  FlexDecodeJsonWith
    (f :: Type -> Type)
    (l1 :: RowList)
    (r1 :: # Type)
    (r0 :: # Type)
    | r0 -> r1 l1 where
    flexDecodeJsonWith'
      :: Record r1
      -> Json
      -> f (Record r0)

instance flexDecodeJsonWithDecodeJsonWith_
  :: ( FlexDecodeCases f l1 r0
     , FlexDecodeJsonWith_ f l1 r1 l0 r0
     , RowToList r0 l0
     , RowToList r1 l1
     , Status f
     )
  => FlexDecodeJsonWith f l1 r1 r0
  where
  flexDecodeJsonWith' decoderRecord =
    reportJson $
      __flexDecodeJsonWith
        (RLProxy :: RLProxy l0)
        (RLProxy :: RLProxy l1)
        decoderRecord

class FlexDecodeJsonWith_
  (f :: Type -> Type)
  (l1 :: RowList)
  (r1 :: # Type)
  (l0 :: RowList)
  (r0 :: # Type)
  | l1 -> r1 l0 r0 where
  __flexDecodeJsonWith
    :: RLProxy l0
    -> RLProxy l1
    -> Record r1
    -> Object Json
    -> f (Record r0)

instance __flexDecodeJsonWithNil
  :: Status f
  => FlexDecodeJsonWith_ f Nil () Nil () where
  __flexDecodeJsonWith _ _ _ _ = report {}

instance __flexDecodeJsonWithCons
  :: ( Alternative f
     , Cons field (f value) row' row
     , Cons field decoderValue decoderRow' decoderRow
     , FlexDecodeCases g decoderList row
     , FlexDecodeCases g decoderList' row'
     , FlexDecodeJsonWith_ g decoderList' decoderRow' list' row'
     , IsSymbol field
     , Lacks field row'
     , Lacks field decoderRow'
     , Monad g
     , RowToList row list
     , RowToList row' list'
     , RowToList decoderRow decoderList
     , RowToList decoderRow' decoderList'
     , Status g
     , TypeEquals decoderValue (Json -> g (f value))
     )
  => FlexDecodeJsonWith_
       g
       (Cons field decoderValue decoderList')
       decoderRow
       (Cons field (f value) list')
       row
  where
  __flexDecodeJsonWith _ _ decoderRecord object = do
    let
      sProxy :: SProxy field
      sProxy = SProxy

      fieldName :: String
      fieldName = reflectSymbol sProxy

      decoder :: Json -> g (f value)
      decoder = to $ get sProxy decoderRecord

    rest <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list')
        (RLProxy :: RLProxy decoderList')
        (delete sProxy decoderRecord)
        object

    case lookup fieldName object of
      Just jsonVal -> do
        val <- decoder jsonVal
        report $ insert sProxy val rest
      Nothing ->
        report $ insert sProxy empty rest

flexDecodeJsonWith
  :: forall decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . FlexDecodeJsonWith_ f decoderList decoderRow list0 row0
  => GDecodeJson row1 list1
  => Monad f
  => Nub row2 row2
  => RowToList row1 list1
  => RowToList decoderRow decoderList
  => RowToList row2 list2
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
flexDecodeJsonWith decoderRecord = reportJson $ go decoderRecord
  where
  go :: Record decoderRow -> Object Json -> f (Record row2)
  go decoderRecord object = do
    record0 <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
    reportObject (merge record0) object (RLProxy :: RLProxy list1)

flexDecodeJsonWith_
  :: forall decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . FlexDecodeJsonWith_ f decoderList decoderRow list0 row0
  => GDecodeJson row1 list1
  => Monad f
  => RowToList row1 list1
  => RowToList decoderRow decoderList
  => RowToList row2 list2
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
flexDecodeJsonWith_ decoderRecord = reportJson $ go decoderRecord
  where
  go :: Record decoderRow -> Object Json -> f (Record row2)
  go decoderRecord object = do
    record0 <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
    reportObject (union record0) object (RLProxy :: RLProxy list1)

flexDecodeJsonWithBoth
  :: forall
       decoderList0
       decoderList1
       decoderRow0
       decoderRow1
       f
       list0
       list1
       list2
       row0
       row1
       row2
   . DecodeJsonWith_     f decoderList0 decoderRow0 list0 row0
  => FlexDecodeJsonWith_ f decoderList1 decoderRow1 list1 row1
  => Monad f
  => Nub row2 row2
  => RowToList row0 list0
  => RowToList row1 list1
  => RowToList row2 list2
  => RowToList decoderRow0 decoderList0
  => RowToList decoderRow1 decoderList1
  => Status f
  => Union row0 row1 row2
  => Record decoderRow0
  -> Record decoderRow1
  -> Json
  -> f (Record row2)
flexDecodeJsonWithBoth decoderRecord0 decoderRecord1 = reportJson go
  where
  go :: Object Json -> f (Record row2)
  go object = do
    record0 <-
      __decodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList0)
        decoderRecord0
        object
    record1 <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list1)
        (RLProxy :: RLProxy decoderList1)
        decoderRecord1
        object
    report $ merge record0 record1

flexDecodeJsonWithBoth_
  :: forall
       decoderList0
       decoderList1
       decoderRow0
       decoderRow1
       f
       list0
       list1
       list2
       row0
       row1
       row2
   . DecodeJsonWith_     f decoderList0 decoderRow0 list0 row0
  => FlexDecodeJsonWith_ f decoderList1 decoderRow1 list1 row1
  => Monad f
  => RowToList row0 list0
  => RowToList row1 list1
  => RowToList row2 list2
  => RowToList decoderRow0 decoderList0
  => RowToList decoderRow1 decoderList1
  => Status f
  => Union row0 row1 row2
  => Record decoderRow0
  -> Record decoderRow1
  -> Json
  -> f (Record row2)
flexDecodeJsonWithBoth_ decoderRecord0 decoderRecord1 = reportJson go
  where
  go :: Object Json -> f (Record row2)
  go object = do
    record0 <-
      __decodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList0)
        decoderRecord0
        object
    record1 <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list1)
        (RLProxy :: RLProxy decoderList1)
        decoderRecord1
        object
    report $ union record0 record1

@soraismus
Copy link
Author

module Data.Argonaut.FlexDecode where

import Prelude

import Control.Alternative (class Alternative, empty)
import Data.Argonaut.Core (Json, toObject)
import Data.Argonaut.Decode.Class
  ( class DecodeJson
  , class GDecodeJson
  , decodeJson
  , gDecodeJson
  )
import Data.Either (Either(Left, Right))
import Data.Maybe (Maybe(Just, Nothing))
import Data.Status.Class (class Status, report, reportError)
import Data.Symbol (class IsSymbol, SProxy(SProxy), reflectSymbol)
import Foreign.Object (Object, lookup)
import Record (delete, get, insert, merge, union)
import Type.Data.RowList (RLProxy(RLProxy)) -- Argonaut dependency
import Type.Equality (class TypeEquals, to)
import Type.Proxy (Proxy(Proxy))
import Type.Row
  ( kind RowList
  , Cons
  , Nil
  , RProxy(RProxy)
  , class Cons
  , class Lacks
  , class Nub
  , class RowToList
  , class Union
  )
import Type.Row as Row

_decodeJson :: forall a. DecodeJson a => Proxy a -> Json -> Either String a
_decodeJson _ = decodeJson

reportJson
  :: forall f r
   . Status f
  => (Object Json -> f (Record r))
  -> Json
  -> f (Record r)
reportJson f json =
  case toObject json of
    Just object -> f object
    Nothing -> reportError "Could not convert JSON to object"

reportObject
  :: forall f l r
   . GDecodeJson r l
  => RowToList r l
  => Status f
  => Object Json
  -> RLProxy l
  -> f (Record r)
reportObject object rlProxy =
  case gDecodeJson object rlProxy of
    Left errorStr -> reportError errorStr
    Right record -> report record

class FlexGDecodeJson f (row :: # Type) (list :: RowList) | list -> row where
  flexGDecodeJson :: Object Json -> RLProxy list -> f (Record row)

instance flexGDecodeJsonCons
  :: ( Alternative f
     , Cons field (f value) rowTail row
     , DecodeJson value
     , FlexGDecodeJson g rowTail tail
     , IsSymbol field
     , Lacks field rowTail
     , Monad g
     , Status g
     )
  => FlexGDecodeJson g row (Cons field (f value) tail) where
  flexGDecodeJson object _ = do
    let sProxy = SProxy :: SProxy field
    let fieldName = reflectSymbol sProxy
    rest <- flexGDecodeJson object (RLProxy :: RLProxy tail)
    case lookup fieldName object of
      Just jsonVal ->
        case decodeJson jsonVal of
          Left errorStr -> reportError errorStr
          Right val     -> report $ insert sProxy (pure val) rest
      Nothing ->
        report $ insert sProxy empty rest

instance flexGDecodeJsonNil
  :: Status f
  => FlexGDecodeJson f () Nil where
  flexGDecodeJson _ _ = report {}

class FlexDecodeJson f a where
  flexDecodeJson' :: Json -> f a

instance flexDecodeJsonRecord
  :: ( FlexGDecodeJson f row list
     , RowToList row list
     , Status f
     )
  => FlexDecodeJson f (Record row) where
  flexDecodeJson' = reportJson $ flip flexGDecodeJson (RLProxy :: RLProxy list)

flexDecodeJson
  :: forall f list0 list1 list2 row0 row1 row2
   . FlexGDecodeJson f row0 list0
  => GDecodeJson row1 list1
  => Monad f
  => Nub row2 row2
  => RowToList row0 list0
  => RowToList row1 list1
  => RowToList row2 list2
  => Status f
  => Union row0 row1 row2
  => RProxy row0
  -> Json
  -> f (Record row2)
flexDecodeJson _ = reportJson go
  where
  go object = do
    record0 <- flexGDecodeJson object (RLProxy :: RLProxy list0)
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    report $ merge record0 record1

flexDecodeJson_
  :: forall f list0 list1 list2 row0 row1 row2
   . FlexGDecodeJson f row0 list0
  => GDecodeJson row1 list1
  => Monad f
  => RowToList row0 list0
  => RowToList row1 list1
  => RowToList row2 list2
  => Status f
  => Union row0 row1 row2
  => RProxy row0
  -> Json
  -> f (Record row2)
flexDecodeJson_ _ = reportJson go
  where
  go object = do
    record0 <- flexGDecodeJson object (RLProxy :: RLProxy list0)
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    report $ union record0 record1

class DecodeCases
  (f :: Type -> Type)
  (list :: RowList)
  (row :: # Type)
  | list -> row

instance decodeCasesCons
  :: ( Cons field value row' row
     , DecodeCases f list' row'
     , TypeEquals decoderValue (Json -> f value)
     )
  => DecodeCases f (Cons field decoderValue list') row

instance decodeCasesNil :: DecodeCases f Nil ()

class
  ( DecodeCases f l1 r0
  , RowToList r1 l1
  ) <=
  DecodeJsonWith
    (f :: Type -> Type)
    (l1 :: RowList)
    (r1 :: # Type)
    (r0 :: # Type)
    | r0 -> r1 l1 where
    decodeJsonWith'
      :: Record r1
      -> Json
      -> f (Record r0)

instance decodeJsonWithDecodeJsonWith_
  :: ( DecodeCases f l1 r0
     , DecodeJsonWith_ f l1 r1 l0 r0
     , RowToList r0 l0
     , RowToList r1 l1
     , Status f
     )
  => DecodeJsonWith f l1 r1 r0
  where
  decodeJsonWith' decoderRecord =
    reportJson $
      __decodeJsonWith
        (RLProxy :: RLProxy l0)
        (RLProxy :: RLProxy l1)
        decoderRecord

class DecodeJsonWith_
  (f :: Type -> Type)
  (l1 :: RowList)
  (r1 :: # Type)
  (l0 :: RowList)
  (r0 :: # Type)
  | l1 -> r1 l0 r0 where
  __decodeJsonWith
    :: RLProxy l0
    -> RLProxy l1
    -> Record r1
    -> Object Json
    -> f (Record r0)

instance __decodeJsonWithNil
  :: Status f
  => DecodeJsonWith_ f Nil () Nil () where
  __decodeJsonWith _ _ _ _ = report {}

instance __decodeJsonWithCons
  :: ( Cons field value row' row
     , Cons field decoderValue decoderRow' decoderRow
     , DecodeCases f decoderList row
     , DecodeCases f decoderList' row'
     , DecodeJsonWith_ f decoderList' decoderRow' list' row'
     , IsSymbol field
     , Lacks field row'
     , Lacks field decoderRow'
     , Monad f
     , RowToList row list
     , RowToList row' list'
     , RowToList decoderRow decoderList
     , RowToList decoderRow' decoderList'
     , Status f
     , TypeEquals decoderValue (Json -> f value)
     )
  => DecodeJsonWith_
       f
       (Cons field decoderValue decoderList')
       decoderRow
       (Cons field value list')
       row
  where
  __decodeJsonWith _ _ decoderRecord object = do
    let
      sProxy :: SProxy field
      sProxy = SProxy

      fieldName :: String
      fieldName = reflectSymbol sProxy

      decoder :: Json -> f value
      decoder = to $ get sProxy decoderRecord

    rest <-
      __decodeJsonWith
        (RLProxy :: RLProxy list')
        (RLProxy :: RLProxy decoderList')
        (delete sProxy decoderRecord)
        object

    case lookup fieldName object of
      Just jsonVal -> do
        val <- decoder jsonVal
        report $ insert sProxy val rest
      Nothing ->
        reportError $ "JSON was missing expected field: " <> fieldName

decodeJsonWith
  :: forall decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . DecodeJsonWith_ f decoderList decoderRow list0 row0
  => GDecodeJson row1 list1
  => Monad f
  => Nub row2 row2
  => RowToList row1 list1
  => RowToList row2 list2
  => RowToList decoderRow decoderList
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
decodeJsonWith decoderRecord = reportJson go
  where
  go :: Object Json -> f (Record row2)
  go object = do
    record0 <-
      __decodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    report $ merge record0 record1

decodeJsonWith_
  :: forall decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . DecodeJsonWith_ f decoderList decoderRow list0 row0
  => GDecodeJson row1 list1
  => Monad f
  => RowToList row1 list1
  => RowToList row2 list2
  => RowToList decoderRow decoderList
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
decodeJsonWith_ decoderRecord = reportJson go
  where
  go :: Object Json -> f (Record row2)
  go object = do
    record0 <-
      __decodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    report $ union record0 record1

class FlexDecodeCases
  (f :: Type -> Type)
  (list :: RowList)
  (row :: # Type)
  | list -> row

instance flexDecodeCasesCons
  :: ( Cons field (f value) row' row
     , FlexDecodeCases g list' row'
     , TypeEquals decoderValue (Json -> g (f value))
     )
  => FlexDecodeCases g (Cons field decoderValue list') row

instance flexDecodeCasesNil :: FlexDecodeCases f Nil ()

class
  ( FlexDecodeCases f l1 r0
  , RowToList r1 l1
  ) <=
  FlexDecodeJsonWith
    (f :: Type -> Type)
    (l1 :: RowList)
    (r1 :: # Type)
    (r0 :: # Type)
    | r0 -> r1 l1 where
    flexDecodeJsonWith'
      :: Record r1
      -> Json
      -> f (Record r0)

instance flexDecodeJsonWithDecodeJsonWith_
  :: ( FlexDecodeCases f l1 r0
     , FlexDecodeJsonWith_ f l1 r1 l0 r0
     , RowToList r0 l0
     , RowToList r1 l1
     , Status f
     )
  => FlexDecodeJsonWith f l1 r1 r0
  where
  flexDecodeJsonWith' decoderRecord =
    reportJson $
      __flexDecodeJsonWith
        (RLProxy :: RLProxy l0)
        (RLProxy :: RLProxy l1)
        decoderRecord

class FlexDecodeJsonWith_
  (f :: Type -> Type)
  (l1 :: RowList)
  (r1 :: # Type)
  (l0 :: RowList)
  (r0 :: # Type)
  | l1 -> r1 l0 r0 where
  __flexDecodeJsonWith
    :: RLProxy l0
    -> RLProxy l1
    -> Record r1
    -> Object Json
    -> f (Record r0)

instance __flexDecodeJsonWithNil
  :: Status f
  => FlexDecodeJsonWith_ f Nil () Nil () where
  __flexDecodeJsonWith _ _ _ _ = report {}

instance __flexDecodeJsonWithCons
  :: ( Alternative f
     , Cons field (f value) row' row
     , Cons field decoderValue decoderRow' decoderRow
     , FlexDecodeCases g decoderList row
     , FlexDecodeCases g decoderList' row'
     , FlexDecodeJsonWith_ g decoderList' decoderRow' list' row'
     , IsSymbol field
     , Lacks field row'
     , Lacks field decoderRow'
     , Monad g
     , RowToList row list
     , RowToList row' list'
     , RowToList decoderRow decoderList
     , RowToList decoderRow' decoderList'
     , Status g
     , TypeEquals decoderValue (Json -> g (f value))
     )
  => FlexDecodeJsonWith_
       g
       (Cons field decoderValue decoderList')
       decoderRow
       (Cons field (f value) list')
       row
  where
  __flexDecodeJsonWith _ _ decoderRecord object = do
    let
      sProxy :: SProxy field
      sProxy = SProxy

      fieldName :: String
      fieldName = reflectSymbol sProxy

      decoder :: Json -> g (f value)
      decoder = to $ get sProxy decoderRecord

    rest <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list')
        (RLProxy :: RLProxy decoderList')
        (delete sProxy decoderRecord)
        object

    case lookup fieldName object of
      Just jsonVal -> do
        val <- decoder jsonVal
        report $ insert sProxy val rest
      Nothing ->
        report $ insert sProxy empty rest

flexDecodeJsonWith
  :: forall decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . FlexDecodeJsonWith_ f decoderList decoderRow list0 row0
  => GDecodeJson row1 list1
  => Monad f
  => Nub row2 row2
  => RowToList row1 list1
  => RowToList decoderRow decoderList
  => RowToList row2 list2
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
flexDecodeJsonWith decoderRecord = reportJson $ go decoderRecord
  where
  go :: Record decoderRow -> Object Json -> f (Record row2)
  go decoderRecord object = do
    record0 <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    report $ merge record0 record1

flexDecodeJsonWith_
  :: forall decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . FlexDecodeJsonWith_ f decoderList decoderRow list0 row0
  => GDecodeJson row1 list1
  => Monad f
  => RowToList row1 list1
  => RowToList decoderRow decoderList
  => RowToList row2 list2
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
flexDecodeJsonWith_ decoderRecord = reportJson $ go decoderRecord
  where
  go :: Record decoderRow -> Object Json -> f (Record row2)
  go decoderRecord object = do
    record0 <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    report $ union record0 record1

flexDecodeJsonWithBoth
  :: forall
       decoderList0
       decoderList1
       decoderRow0
       decoderRow1
       f
       intermediateRow
       list0
       list1
       list2
       row0
       row1
       row2
       row3
   . DecodeJsonWith_ f decoderList0 decoderRow0 list0 row0
  => FlexDecodeJsonWith_ f decoderList1 decoderRow1 list1 row1
  => GDecodeJson row2 list2
  => Monad f
  => Nub intermediateRow intermediateRow
  => Nub row3 row3
  => RowToList decoderRow0 decoderList0
  => RowToList decoderRow1 decoderList1
  => RowToList row0 list0
  => RowToList row1 list1
  => RowToList row2 list2
  => Status f
  => Union row0 row1 intermediateRow
  => Union intermediateRow row2 row3
  => Record decoderRow0
  -> Record decoderRow1
  -> Json
  -> f (Record row3)
flexDecodeJsonWithBoth decoderRecord0 decoderRecord1 = reportJson go
  where
  go :: Object Json -> f (Record row3)
  go object = do
    record0 <-
      __decodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList0)
        decoderRecord0
        object
    record1 <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list1)
        (RLProxy :: RLProxy decoderList1)
        decoderRecord1
        object
    record2 <- reportObject object (RLProxy :: RLProxy list2)
    report $ record0 `union` record1 `union` record2

flexDecodeJsonWithBoth_
  :: forall
       decoderList0
       decoderList1
       decoderRow0
       decoderRow1
       f
       intermediateRow
       list0
       list1
       list2
       row0
       row1
       row2
       row3
   . DecodeJsonWith_ f decoderList0 decoderRow0 list0 row0
  => FlexDecodeJsonWith_ f decoderList1 decoderRow1 list1 row1
  => GDecodeJson row2 list2
  => Monad f
  => RowToList decoderRow0 decoderList0
  => RowToList decoderRow1 decoderList1
  => RowToList row0 list0
  => RowToList row1 list1
  => RowToList row2 list2
  => Status f
  => Union row0 row1 intermediateRow
  => Union intermediateRow row2 row3
  => Record decoderRow0
  -> Record decoderRow1
  -> Json
  -> f (Record row3)
flexDecodeJsonWithBoth_ decoderRecord0 decoderRecord1 = reportJson go
  where
  go :: Object Json -> f (Record row3)
  go object = do
    record0 <-
      __decodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList0)
        decoderRecord0
        object
    record1 <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list1)
        (RLProxy :: RLProxy decoderList1)
        decoderRecord1
        object
    record2 <- reportObject object (RLProxy :: RLProxy list2)
    report $ record0 `union` record1 `union` record2

@soraismus
Copy link
Author

-- are all instances of DecodeCases/TypeEquals necessary
-- still too many copies of of json/object error message
-- maybe the heterogenous library pertains to this; is this redundant?
-- 4 cases: 0. gdecode, 1. gdecode leniently, 2. override, 3. override w/ leniency
-- the others can be based on the general case
module Data.Argonaut.FlexDecode where

import Prelude

import Control.Alternative (class Alternative, empty)
import Data.Argonaut.Core (Json, toObject)
import Data.Argonaut.Decode.Class
  ( class DecodeJson
  , class GDecodeJson
  , decodeJson
  , gDecodeJson
  )
import Data.Either (Either(Left, Right))
import Data.Maybe (Maybe(Just, Nothing))
import Data.Status.Class (class Status, report, reportError)
import Data.Symbol (class IsSymbol, SProxy(SProxy), reflectSymbol)
import Foreign.Object (Object, lookup)
import Record (delete, get, insert, merge, union)
import Type.Data.RowList (RLProxy(RLProxy)) -- Argonaut dependency
import Type.Equality (class TypeEquals, to)
import Type.Proxy (Proxy(Proxy))
import Type.Row
  ( kind RowList
  , Cons
  , Nil
  , RProxy(RProxy)
  , class Cons
  , class Lacks
  , class Nub
  , class RowToList
  , class Union
  )
import Type.Row as Row

_decodeJson :: forall a. DecodeJson a => Proxy a -> Json -> Either String a
_decodeJson _ = decodeJson

reportJson
  :: forall f r
   . Status f
  => (Object Json -> f (Record r))
  -> Json
  -> f (Record r)
reportJson f json =
  case toObject json of
    Just object -> f object
    Nothing -> reportError "Could not convert JSON to object"

reportObject
  :: forall f l r
   . GDecodeJson r l
  => RowToList r l
  => Status f
  => Object Json
  -> RLProxy l
  -> f (Record r)
reportObject object rlProxy =
  case gDecodeJson object rlProxy of
    Left errorStr -> reportError errorStr
    Right record -> report record

class FlexGDecodeJson f (row :: # Type) (list :: RowList) | list -> row where
  flexGDecodeJson :: Object Json -> RLProxy list -> f (Record row)

instance flexGDecodeJsonCons
  :: ( Alternative f
     , Cons field (f value) rowTail row
     , DecodeJson value
     , FlexGDecodeJson g rowTail tail
     , IsSymbol field
     , Lacks field rowTail
     , Monad g
     , Status g
     )
  => FlexGDecodeJson g row (Cons field (f value) tail) where
  flexGDecodeJson object _ = do
    let sProxy = SProxy :: SProxy field
    let fieldName = reflectSymbol sProxy
    rest <- flexGDecodeJson object (RLProxy :: RLProxy tail)
    case lookup fieldName object of
      Just jsonVal ->
        case decodeJson jsonVal of
          Left errorStr -> reportError errorStr
          Right val     -> report $ insert sProxy (pure val) rest
      Nothing ->
        report $ insert sProxy empty rest

instance flexGDecodeJsonNil
  :: Status f
  => FlexGDecodeJson f () Nil where
  flexGDecodeJson _ _ = report {}

class FlexDecodeJson f a where
  flexDecodeJson' :: Json -> f a

instance flexDecodeJsonRecord
  :: ( FlexGDecodeJson f row list
     , RowToList row list
     , Status f
     )
  => FlexDecodeJson f (Record row) where
  flexDecodeJson' = reportJson $ flip flexGDecodeJson (RLProxy :: RLProxy list)

flexDecodeJson
  :: forall f list0 list1 list2 row0 row1 row2
   . FlexGDecodeJson f row0 list0
  => GDecodeJson row1 list1
  => Monad f
  => Nub row2 row2
  => RowToList row0 list0
  => RowToList row1 list1
  => RowToList row2 list2
  => Status f
  => Union row0 row1 row2
  => RProxy row0
  -> Json
  -> f (Record row2)
flexDecodeJson _ = reportJson go
  where
  go object = do
    record0 <- flexGDecodeJson object (RLProxy :: RLProxy list0)
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    report $ merge record0 record1

flexDecodeJson_
  :: forall f list0 list1 list2 row0 row1 row2
   . FlexGDecodeJson f row0 list0
  => GDecodeJson row1 list1
  => Monad f
  => RowToList row0 list0
  => RowToList row1 list1
  => RowToList row2 list2
  => Status f
  => Union row0 row1 row2
  => RProxy row0
  -> Json
  -> f (Record row2)
flexDecodeJson_ _ = reportJson go
  where
  go object = do
    record0 <- flexGDecodeJson object (RLProxy :: RLProxy list0)
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    report $ union record0 record1

class DecodeCases
  (f :: Type -> Type)
  (list :: RowList)
  (row :: # Type)
  | list -> row

instance decodeCasesCons
  :: ( Cons field value row' row
     , DecodeCases f list' row'
     , TypeEquals decoderValue (Json -> f value)
     )
  => DecodeCases f (Cons field decoderValue list') row

instance decodeCasesNil :: DecodeCases f Nil ()

class
  ( DecodeCases f l1 r0
  , RowToList r1 l1
  ) <=
  DecodeJsonWith
    (f :: Type -> Type)
    (l1 :: RowList)
    (r1 :: # Type)
    (r0 :: # Type)
    | r0 -> r1 l1 where
    decodeJsonWith'
      :: Record r1
      -> Json
      -> f (Record r0)

instance decodeJsonWithDecodeJsonWith_
  :: ( DecodeCases f l1 r0
     , DecodeJsonWith_ f l1 r1 l0 r0
     , RowToList r0 l0
     , RowToList r1 l1
     , Status f
     )
  => DecodeJsonWith f l1 r1 r0
  where
  decodeJsonWith' decoderRecord =
    reportJson $
      __decodeJsonWith
        (RLProxy :: RLProxy l0)
        (RLProxy :: RLProxy l1)
        decoderRecord

class DecodeJsonWith_
  (f :: Type -> Type)
  (l1 :: RowList)
  (r1 :: # Type)
  (l0 :: RowList)
  (r0 :: # Type)
  | l1 -> r1 l0 r0 where
  __decodeJsonWith
    :: RLProxy l0
    -> RLProxy l1
    -> Record r1
    -> Object Json
    -> f (Record r0)

instance __decodeJsonWithNil
  :: Status f
  => DecodeJsonWith_ f Nil () Nil () where
  __decodeJsonWith _ _ _ _ = report {}

instance __decodeJsonWithCons
  :: ( Cons field value row' row
     , Cons field decoderValue decoderRow' decoderRow
     , DecodeCases f decoderList row
     , DecodeCases f decoderList' row'
     , DecodeJsonWith_ f decoderList' decoderRow' list' row'
     , IsSymbol field
     , Lacks field row'
     , Lacks field decoderRow'
     , Monad f
     , RowToList row list
     , RowToList row' list'
     , RowToList decoderRow decoderList
     , RowToList decoderRow' decoderList'
     , Status f
     , TypeEquals decoderValue (Json -> f value)
     )
  => DecodeJsonWith_
       f
       (Cons field decoderValue decoderList')
       decoderRow
       (Cons field value list')
       row
  where
  __decodeJsonWith _ _ decoderRecord object = do
    let
      sProxy :: SProxy field
      sProxy = SProxy

      fieldName :: String
      fieldName = reflectSymbol sProxy

      decoder :: Json -> f value
      decoder = to $ get sProxy decoderRecord

    rest <-
      __decodeJsonWith
        (RLProxy :: RLProxy list')
        (RLProxy :: RLProxy decoderList')
        (delete sProxy decoderRecord)
        object

    case lookup fieldName object of
      Just jsonVal -> do
        val <- decoder jsonVal
        report $ insert sProxy val rest
      Nothing ->
        reportError $ "JSON was missing expected field: " <> fieldName

decodeJsonWith
  :: forall decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . DecodeJsonWith_ f decoderList decoderRow list0 row0
  => GDecodeJson row1 list1
  => Monad f
  => Nub row2 row2
  => RowToList row1 list1
  => RowToList row2 list2
  => RowToList decoderRow decoderList
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
decodeJsonWith decoderRecord = reportJson go
  where
  go :: Object Json -> f (Record row2)
  go object = do
    record0 <-
      __decodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    report $ merge record0 record1

decodeJsonWith_
  :: forall decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . DecodeJsonWith_ f decoderList decoderRow list0 row0
  => GDecodeJson row1 list1
  => Monad f
  => RowToList row1 list1
  => RowToList row2 list2
  => RowToList decoderRow decoderList
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
decodeJsonWith_ decoderRecord = reportJson go
  where
  go :: Object Json -> f (Record row2)
  go object = do
    record0 <-
      __decodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    report $ union record0 record1

class FlexDecodeCases
  (f :: Type -> Type)
  (list :: RowList)
  (row :: # Type)
  | list -> row

instance flexDecodeCasesCons
  :: ( Cons field (f value) row' row
     , FlexDecodeCases g list' row'
     , TypeEquals decoderValue (Json -> g (f value))
     )
  => FlexDecodeCases g (Cons field decoderValue list') row

instance flexDecodeCasesNil :: FlexDecodeCases f Nil ()

class
  ( FlexDecodeCases f l1 r0
  , RowToList r1 l1
  ) <=
  FlexDecodeJsonWith
    (f :: Type -> Type)
    (l1 :: RowList)
    (r1 :: # Type)
    (r0 :: # Type)
    | r0 -> r1 l1 where
    flexDecodeJsonWith'
      :: Record r1
      -> Json
      -> f (Record r0)

instance flexDecodeJsonWithDecodeJsonWith_
  :: ( FlexDecodeCases f l1 r0
     , FlexDecodeJsonWith_ f l1 r1 l0 r0
     , RowToList r0 l0
     , RowToList r1 l1
     , Status f
     )
  => FlexDecodeJsonWith f l1 r1 r0
  where
  flexDecodeJsonWith' decoderRecord =
    reportJson $
      __flexDecodeJsonWith
        (RLProxy :: RLProxy l0)
        (RLProxy :: RLProxy l1)
        decoderRecord

class FlexDecodeJsonWith_
  (f :: Type -> Type)
  (l1 :: RowList)
  (r1 :: # Type)
  (l0 :: RowList)
  (r0 :: # Type)
  | l1 -> r1 l0 r0 where
  __flexDecodeJsonWith
    :: RLProxy l0
    -> RLProxy l1
    -> Record r1
    -> Object Json
    -> f (Record r0)

instance __flexDecodeJsonWithNil
  :: Status f
  => FlexDecodeJsonWith_ f Nil () Nil () where
  __flexDecodeJsonWith _ _ _ _ = report {}

instance __flexDecodeJsonWithCons
  :: ( Alternative f
     , Cons field (f value) row' row
     , Cons field decoderValue decoderRow' decoderRow
     , FlexDecodeCases g decoderList row
     , FlexDecodeCases g decoderList' row'
     , FlexDecodeJsonWith_ g decoderList' decoderRow' list' row'
     , IsSymbol field
     , Lacks field row'
     , Lacks field decoderRow'
     , Monad g
     , RowToList row list
     , RowToList row' list'
     , RowToList decoderRow decoderList
     , RowToList decoderRow' decoderList'
     , Status g
     , TypeEquals decoderValue (Json -> g (f value))
     )
  => FlexDecodeJsonWith_
       g
       (Cons field decoderValue decoderList')
       decoderRow
       (Cons field (f value) list')
       row
  where
  __flexDecodeJsonWith _ _ decoderRecord object = do
    let
      sProxy :: SProxy field
      sProxy = SProxy

      fieldName :: String
      fieldName = reflectSymbol sProxy

      decoder :: Json -> g (f value)
      decoder = to $ get sProxy decoderRecord

    rest <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list')
        (RLProxy :: RLProxy decoderList')
        (delete sProxy decoderRecord)
        object

    case lookup fieldName object of
      Just jsonVal -> do
        val <- decoder jsonVal
        report $ insert sProxy val rest
      Nothing ->
        report $ insert sProxy empty rest

flexDecodeJsonWith
  :: forall decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . FlexDecodeJsonWith_ f decoderList decoderRow list0 row0
  => GDecodeJson row1 list1
  => Monad f
  => Nub row2 row2
  => RowToList row1 list1
  => RowToList decoderRow decoderList
  => RowToList row2 list2
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
flexDecodeJsonWith decoderRecord = reportJson $ go decoderRecord
  where
  go :: Record decoderRow -> Object Json -> f (Record row2)
  go decoderRecord object = do
    record0 <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    report $ merge record0 record1

flexDecodeJsonWith_
  :: forall decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . FlexDecodeJsonWith_ f decoderList decoderRow list0 row0
  => GDecodeJson row1 list1
  => Monad f
  => RowToList row1 list1
  => RowToList decoderRow decoderList
  => RowToList row2 list2
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
flexDecodeJsonWith_ decoderRecord = reportJson $ go decoderRecord
  where
  go :: Record decoderRow -> Object Json -> f (Record row2)
  go decoderRecord object = do
    record0 <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    report $ union record0 record1

flexDecodeJsonWithBoth
  :: forall
       decoderList0
       decoderList1
       decoderRow0
       decoderRow1
       f
       intermediateRow
       list0
       list1
       list2
       row0
       row1
       row2
       row3
   . DecodeJsonWith_ f decoderList0 decoderRow0 list0 row0
  => FlexDecodeJsonWith_ f decoderList1 decoderRow1 list1 row1
  => GDecodeJson row2 list2
  => Monad f
  => Nub intermediateRow intermediateRow
  => Nub row3 row3
  => RowToList decoderRow0 decoderList0
  => RowToList decoderRow1 decoderList1
  => RowToList row0 list0
  => RowToList row1 list1
  => RowToList row2 list2
  => Status f
  => Union row0 row1 intermediateRow
  => Union intermediateRow row2 row3
  => Record decoderRow0
  -> Record decoderRow1
  -> Json
  -> f (Record row3)
flexDecodeJsonWithBoth decoderRecord0 decoderRecord1 = reportJson go
  where
  go :: Object Json -> f (Record row3)
  go object = do
    record0 <-
      __decodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList0)
        decoderRecord0
        object
    record1 <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list1)
        (RLProxy :: RLProxy decoderList1)
        decoderRecord1
        object
    record2 <- reportObject object (RLProxy :: RLProxy list2)
    report $ record0 `union` record1 `union` record2

flexDecodeJsonWithBoth_
  :: forall
       decoderList0
       decoderList1
       decoderRow0
       decoderRow1
       f
       intermediateRow
       list0
       list1
       list2
       row0
       row1
       row2
       row3
   . DecodeJsonWith_ f decoderList0 decoderRow0 list0 row0
  => FlexDecodeJsonWith_ f decoderList1 decoderRow1 list1 row1
  => GDecodeJson row2 list2
  => Monad f
  => RowToList decoderRow0 decoderList0
  => RowToList decoderRow1 decoderList1
  => RowToList row0 list0
  => RowToList row1 list1
  => RowToList row2 list2
  => Status f
  => Union row0 row1 intermediateRow
  => Union intermediateRow row2 row3
  => Record decoderRow0
  -> Record decoderRow1
  -> Json
  -> f (Record row3)
flexDecodeJsonWithBoth_ decoderRecord0 decoderRecord1 = reportJson go
  where
  go :: Object Json -> f (Record row3)
  go object = do
    record0 <-
      __decodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList0)
        decoderRecord0
        object
    record1 <-
      __flexDecodeJsonWith
        (RLProxy :: RLProxy list1)
        (RLProxy :: RLProxy decoderList1)
        decoderRecord1
        object
    record2 <- reportObject object (RLProxy :: RLProxy list2)
    report $ record0 `union` record1 `union` record2

-- | -------------------------------------------------------------------------

class XDecodeCases
  (f :: Type -> Type)
  (list :: RowList)
  (row :: # Type)
  a
  | list -> row

instance xDecodeCasesCons
  :: ( Cons field value row' row
     , XDecodeCases f list' row' a
     , TypeEquals decoderValue (Json -> a -> f value)
     )
  => XDecodeCases f (Cons field decoderValue list') row a

instance xDecodeCasesNil :: XDecodeCases f Nil () a

class XDecodeJsonWith_
  (f :: Type -> Type)
  (l1 :: RowList)
  (r1 :: # Type)
  (l0 :: RowList)
  (r0 :: # Type)
  a
  | l1 -> r1 l0 r0 a where
  __xDecodeJsonWith
    :: RLProxy l0
    -> RLProxy l1
    -> Record r1
    -> Object Json
    -> a
    -> f (Record r0)

instance __xDecodeJsonWithNil
  :: Status f
  => XDecodeJsonWith_ f Nil () Nil () a where
  __xDecodeJsonWith _ _ _ _ _ = report {}

instance __xDecodeJsonWithCons
  :: ( Cons field value row' row
     , Cons field decoderValue decoderRow' decoderRow
     , XDecodeCases f decoderList row a
     , XDecodeCases f decoderList' row' a
     , XDecodeJsonWith_ f decoderList' decoderRow' list' row' a
     , IsSymbol field
     , Lacks field row'
     , Lacks field decoderRow'
     , Monad f
     , RowToList row list
     , RowToList row' list'
     , RowToList decoderRow decoderList
     , RowToList decoderRow' decoderList'
     , Status f
     , TypeEquals decoderValue (Json -> a -> f value)
     )
  => XDecodeJsonWith_
       f
       (Cons field decoderValue decoderList')
       decoderRow
       (Cons field value list')
       row
       a
  where
  __xDecodeJsonWith _ _ decoderRecord object x = do
    let
      sProxy :: SProxy field
      sProxy = SProxy

      fieldName :: String
      fieldName = reflectSymbol sProxy

      decoder :: Json -> a -> f value
      decoder = to $ get sProxy decoderRecord

    rest <-
      __xDecodeJsonWith
        (RLProxy :: RLProxy list')
        (RLProxy :: RLProxy decoderList')
        (delete sProxy decoderRecord)
        object
        x

    case lookup fieldName object of
      Just jsonVal -> do
        val <- decoder jsonVal x
        report $ insert sProxy val rest
      Nothing ->
        reportError $ "JSON was missing expected field: " <> fieldName

xDecodeJsonWith
  :: forall a decoderRow decoderList f list0 list1 list2 row0 row1 row2
   . XDecodeJsonWith_ f decoderList decoderRow list0 row0 (Record row1)
  => GDecodeJson row1 list1
  => Monad f
  => Nub row2 row2
  => RowToList row1 list1
  => RowToList row2 list2
  => RowToList decoderRow decoderList
  => Status f
  => Union row0 row1 row2
  => Record decoderRow
  -> Json
  -> f (Record row2)
xDecodeJsonWith decoderRecord = reportJson go
  where
  go :: Object Json -> f (Record row2)
  go object = do
    record1 <- reportObject object (RLProxy :: RLProxy list1)
    record0 <-
      __xDecodeJsonWith
        (RLProxy :: RLProxy list0)
        (RLProxy :: RLProxy decoderList)
        decoderRecord
        object
        record
    report $ merge record0 record1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment