purescript-codec-argonaut
purescript-codec-argonaut copied to clipboard
Add support for optional record properties
I have a rough thing that works for this using an # optionalProp
combinator rather than the record syntactic sugar, but would like to come up with a way that can work for sugar too.
@garyb Could you elaborate/make a hint on what method did or do you have in mind to enable syntactic sugar for optional fields? I tried to tackle it a bit, but it doesn't seem straightforward.
It would mean introducing something similar to the Left
/ Right
for variantMatch
distinguishing between nullary and unary constructors, although I would probably introduce a ADT with better named constructors for it, so you'd have like
CAR.object "MyRecord"
{ foo: CAR.Required CA.string
, bar: CAR.Optional CA.int
}
Ideally I'd like to have a 3rd supported variety too, constant values:
CAR.object "MyRecord"
{ tag: CAR.Constant CA.string "baz"
, foo: CAR.Required CA.string
, bar: CAR.Optional CA.int
}
But that kind of thing doesn't exist in the library in any form right now.
All of this should be pretty easy really, I don't recall what it was that stopped me from just getting it done.
Nice, I also have come to such API after trying to get if it is possible to make it using just JsonCodec
like:
object "MyRecord"
{ tag: string "baz"
, foo: maybe string
, bar: optional CA.int
}
But as a custom type is a way to go, it could potentially be extended to an even nicer API via helper functions for constructing this type:
object "MyRecord"
{ tag: constant string "baz"
, foo: required string # default "foo"
, someBar: optional int # rename "some_bar"
}
Any change a name other than object
could be used to distinguish one from another? If you're doing variant A, could it instead be named objectA
?
@garyb I'm quite bad at type level yet. So, for this to work:
type Obj =
{ x :: Int
, y :: Maybe String
}
recCodec :: JsonCodec Obj
recCodec =
object "Record"
{ x: required C.int
, y: optional C.string
}
I have such a dirty implementation:
data RecordPropCodec a
= Required (JsonCodec (Maybe a))
| Optional (JsonCodec a)
instance rowListCodecCons ::
( RowListCodec rs ri' ro'
, Row.Cons sym (RecordPropCodec a) ri' ri
, Row.Cons sym a ro' ro
, IsSymbol sym
, TE.TypeEquals co (RecordPropCodec a)
) => RowListCodec (RL.Cons sym co rs) ri ro where
rowListCodec _ codecs =
case codec of
Required c ->
recordProp (Proxy :: _ sym) (unsafeCoerce c) tail
Optional c ->
recordPropOptional (Proxy :: _ sym) c tail
where
codec = TE.from (Rec.get (Proxy :: Proxy sym) codecs)
optional :: ∀ a. JsonCodec a -> RecordPropCodec (Maybe a)
optional c = Optional (unsafeCoerce c)
required :: ∀ a. JsonCodec a -> RecordPropCodec a
required c = Required (unsafeCoerce c)
Also here in recordPropOptional
instead of Row.Cons p (Maybe a) r r'
have to use Row.Cons p a r r'
because of constraint in the instance.
Should there probably be a more type-wise approach to this?
Fixed in #51