to take a given type and… • Convert values of the type to JSON (serialize) • Convert JSON to a result of values of the type (deserialize) What I don’t want • JSON.parse as String -> a ◦ Just pretending my arbitrary JS object is the correct type/shape is not fun ◦ undefined is not a function ◦ cannot read property ‘sanity’ of undefined ◦ etc.
a JS programmer, then yes, you probably have been • Manually decoding JSON ◦ String -> Hashmap/Object/“Foreign” -> validate/extract properties -> construct object or throw IllegalArgumentException or Result<T> or what have you ▪ You really should not be doing this as it’s manual and error-prone • Annotation-based ◦ Java devs should be doing this with Jackson annotations and such ◦ “I do this with Joi/tcomb schemas” • “Generic”/“Shapeless”/type-level introspection-based ◦ Scala: JSON to case class or w/e, Haskell: Aeson, etc. ◦ Purescript with Foreign-Generic ◦ Rust json::decode?
de/serialization. Typeclasses in a nutshell: “it’s kind of like Interfaces” (cue booing) Currently two Generics approaches exist (might/probably will change by the time someone revisits these slides) 1. Generic (existing) 2. Generic.Rep (newer) a. enables a lot more powerful stuff b. like deriving Is/AsForeign instances for my cruddy JSON
import Data.Generic.Rep as Rep derive instance repGenericSimpleRecord :: Rep.Generic SimpleRecord _ Now we can use any Rep.Generic a _ => [...] readGeneric :: forall a rep. (Generic a rep, GenericDecode rep) => Options -> Foreign -> F a toForeignGeneric :: forall a rep. (Generic a rep, GenericEncode rep) => Options -> a -> Foreign
= readGeneric $ defaultOptions {unwrapSingleConstructors = true} instance asForeignSimpleRecord :: AsForeign SimpleRecord where write = toForeignGeneric $ defaultOptions {unwrapSingleConstructors = true} Options actually quite useful in some cases, as we’ll see later Only four lines of code to do this very explicitly!
a => String -> F a type F a = Except MultipleErrors a type MultipleErrors = NonEmptyList ForeignError Extracting the result: just run it! runExcept :: forall e a. Except e a -> Either e a
are represented in people’s code today • Constants ◦ “Apple”, “Banana”, “Grape” ◦ Takes some effort, though there are some ways it could be done that are less time-efficient • Tagged Objects ◦ { “tag”: “Fruit”, contents: { “color”: “red” } } ◦ We can do this automatically
| Watermelon derive instance repGenericFruit :: Rep.Generic Fruit _ instance showFruit :: Show Fruit where show = genericShow instance isForeignFruit :: IsForeign Fruit where read x = chooseFruit =<< readString x where chooseFruit s | s == show Apple = pure Apple | s == show Banana = pure Banana | s == show Watermelon = pure Watermelon | otherwise = fail $ ForeignError "We don't know what fruit this is!!!" instance asForeignFruit :: AsForeign Fruit where write = toForeign <<< show newtype RecordWithADT = RecordWithADT { fruit :: Fruit } [...]
fruit: Apple }) "{ \"fruit\": \"Watermelon\" }" (Right (RecordWithADT { fruit: Watermelon })) RecordWithADT ✓ can be converted to JSON (RecordWithADT { fruit: Apple }) -> {"fruit":"Apple"} ✓ can be converted back ✓ can be converted from JSON { "fruit": "Watermelon" } -> (Right (RecordWithADT { fruit: Watermelon }))
"{ \"tag\": \"Add\", \"contents\": 123 }" (Right (Add 123)) ADTWithArgs ✓ can be converted to JSON (Set { count: 5 }) -> {"contents":{"count":5},"tag":"Set"} ✓ can be converted back ✓ can be converted from JSON { "tag": "Add", "contents": 123 } -> (Right (Add 123))
"hunter2" }) "{ \"type\": \"Logout\" }" (Right (Logout)) TypicalJSTaggedObject ✓ can be converted to JSON (Login { password: "hunter2", username: "agent" }) -> {"payload":{"username":"agent","password":"hunter2"},"type":"Login"} ✓ can be converted back ✓ can be converted from JSON { "type": "Logout" } -> (Right Logout)
de/serialization can be done largely automatically ◦ Provided your language gives you the tools to do it • You can manually resolve bits as needed ◦ Provided your language gives you the tools to do it • You shouldn’t have to do any of this manually in 2017 ◦ Provided your language gives you the tools to do it • You should write some Purescript too ◦ Or a language of similar power • You should give a similar talk if you’re an F# or OCaml user Conclusion