Skip to content
This repository has been archived by the owner on May 25, 2022. It is now read-only.

Documentation: how do you decode into a record? #13

Open
sboosali opened this issue Nov 19, 2017 · 7 comments
Open

Documentation: how do you decode into a record? #13

sboosali opened this issue Nov 19, 2017 · 7 comments

Comments

@sboosali
Copy link

for example, the equivalent of aeson's eitherDeode.

@Dridus
Copy link
Contributor

Dridus commented Nov 19, 2017

ah good point. probably each module should grow its own README with some more explanation.

in the interim though, you do this via aeson-better-errors, so following the README example it'd be something like:

import Composite.Aeson (fromJsonWithFormat)
import Composite.Record (Record, Rec((:&), RNil), val)
import Data.Aeson.BetterErrors (ParseError, parseValue)
import Data.Aeson.QQ (aesonQQ)

eitherParseErrorOrAlice :: Either (ParseError e) (Record User)
eitherParseErrorOrAlice =
  parseValue (fromJsonWithFormat userFormat)
    [aesonQQ|{"id": 1, "name": "Alice"}|] -- val @"id" 1 :& val @"name" "Alice" :& RNil

@sboosali
Copy link
Author

sboosali commented Nov 19, 2017 via email

@Dridus
Copy link
Contributor

Dridus commented Nov 19, 2017

double thanks! any help is of course appreciated! glad you're enjoying it so far, please do let us know if you have any improvements you think are good to have or other feedback

@sboosali
Copy link
Author

sboosali commented Nov 19, 2017

also, how do nested records work? for example,

type CardRecord = Record Card 
type Card = 
         [ "name"          ::: Text 
         , "legalities"    ::: List (Record [ "format"   ::: Text 
                                            , "legality" ::: Text 
                                            ]) 
         ] 

type LegalityRecord = Record Legality
type Legality = 
  [ "format"   ::: Text 
  , "legality" ::: Text 
  ]
 -- using a type alias btw,
 type (:::) = (:->) 
 type List = ([]) 

I understand the but it's the wrong kind, but why does DefaultJsonFormatRecord take the raw type level list, instead of the record or a proxy-record? my schema has several nested records, and I'd like to derive a formatting to initially explore the json. and relatedly, I can't construct a schema type that doesn't mention records, the Card in this example, because normal type constructors like Maybe and [] need *, not [*] (since the inner record, Legality in this example, is wrapped with another type constructor, I can't just inline its fields). or is it expected to manually construct the formatting for these schemas?

@Dridus
Copy link
Contributor

Dridus commented Nov 19, 2017

nested record encoding is a bit annoying at present because we don't want to force all records to have DefaultJsonFormat typed field values, and instancing DefaultJsonFormat for whatever your particular record type requires funny instance resolution extensions. We typically either use newtypes or explicitly format the subrecord.

newtypes:

data Legality = LegalityBanned | LegalityRestricted | LegalityLegal
  deriving (Bounded, Eq, Generic, Ord, Show)

legalityJsonFormat :: JsonFormat e Legality
legalityJsonFormat = enumJsonFormat "Legality"

instance DefaultJsonFormat Legality where defaultJsonFormat = legalityJsonFormat

type LegalityForFormat = '[ "format" :-> Text, "legality" :-> Legality ]
makeRecordJsonWrapper "LegalityForFormatJson" ''LegalityForFormat

type Card = '[ "name" :-> Text, "legalities" :-> [LegalityForFormatJson] ]
makeRecordJsonWrapper "CardJson" ''Card

pro: fairly easy to do, con: "infects" your data with json-ness, you need to navigate the wrappers

explicit formats:

data Legality = LegalityBanned | LegalityRestricted | LegalityLegal
  deriving (Bounded, Eq, Generic, Ord, Show)

legalityJsonFormat :: JsonFormat e Legality
legalityJsonFormat = enumJsonFormat "Legality"

instance DefaultJsonFormat Legality where defaultJsonFormat = legalityJsonFormat

type LegalityForFormat = '[ "format" :-> Text, "legality" :-> Legality ]
type Card = '[ "name" :-> Text, "legalities" :-> [Record LegalityForFormat] ]

cardJsonFormatRecord :: JsonFormatRecord e Card
cardJsonFormatRecord
  =  field @"name" defaultJsonFormat
  :& field @"legalities" (arrayJsonFormat (recordJsonFormat defaultJsonFormatRecord))
  :& RNil
makeRecordJsonWrapperExplicit "CardJson" ''Card [| cardJsonFormatRecord |]

pro: totally separates JSON-ness from data model, can more easily have more than a single canonical encoding for a particular data model, con: more typing/boilerplate

@torgeirsh
Copy link

@Dridus How funny are the instance resolution extensions? Could you show a quick example similar to the ones above?

@Dridus
Copy link
Contributor

Dridus commented Dec 20, 2019

@torgeirsh it's been a while so my mental cache of the concerns here isn't fresh, but I'll think about it and try to get back to you. my vague recollection is that you'd need IncoherentInstances to define the instances it's looking for, since you're defining them for types you didn't define here

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants