garyb / purescript-codec-argonaut

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

The way to "merge" record codecs #41

Closed wclr closed 3 years ago

wclr commented 3 years ago

If there are types:

type C = { c :: Boolean } -- suppose it may contain many props
type A = { a :: Int, c :: Boolean } -- contains all props from C and some additional
type B = { b :: Number, c :: Boolean } -- contains all props from C and some additional

What would be the best way to get codecs for A and B, but without defining in each of them codecs for properties from C type? Maybe something like merging codecs produced by record (JPropCodec)?

JordanMartinez commented 3 years ago

Ultimately, you'd have to write something like:

mkCodecC :: forall r' r. Cons "c" Boolean r r' => JPropCodec (Record r) -> JPropCodec (Record r')
mkCodecC nextCodec = nextCodec
  # recordProp (Proxy :: Proxy "c") CA.boolean
  -- if you had more fields then...
  -- add more `recordProp`s and add another `Cons`
  -- constraint above
  -- # recordProp (Proxy :: Proxy "nextField") CA.int
  -- # recordProp (Proxy :: Proxy "nextField") CA.string

codec :: JsonCodec A
codecA = record
  # mkCodecC
  # recordProp (Proxy :: Proxy "a") CA.int

If you look at RowListCodec, which is used in the more "convenient" version of encoding a record, object, that's how it constructs the record. It's basically doing a fold over the row via RowList where each iteration adds another key and its codec to the final record codec

wclr commented 3 years ago

@JordanMartinez thanks! Though your code seems to show the error inside mkCodecC, I can not actually figure out what is wrong with it.

  Could not match type

    ( c :: Boolean
    | r0
    )

  with type

    r'1
JordanMartinez commented 3 years ago

@wclr Thanks! Looks like I was wrong, but I found that this compiles (although order matters still):

https://try.purescript.org/?gist=54cd7a2586d9cba35a2ebfa25f930b42

garyb commented 3 years ago

Yeah, there's a few ways of doing it, but you've discovered the important part - you need to define codecs just for the fields (JPropCodec) and turn them into an "actual" codec with CA.record.

It's a bit the codec equivalent of doing something like:

type AProps r = ( a :: Int | r )
type BProps r = ( b :: Number | r )
type CProps r = ( c :: Boolean | r )

type A = Record (AProps + CProps + ())
type B = Record (BProps + CProps  + ())
type C = Record (CProps ())

Basically you need to separate the properties (rows) from the records (objects).