yoga-json is a light-weight and simple json library for purescript.
Note: This library was initially forked from the amazing simple-json (MIT Licence).
Replace your imports from Simple.JSON to Yoga.JSON to migrate.
- 😌 simple and easy to use json codecs
- 🪶 light-weight
- 🤖 built-in support for many common types (
Sumtypes, Tuples, BigInts, Maps, JSDate, DateTime, Eithers, NonEmptyStrings ) - 🖍 human-friendly error reporting
spago install yoga-json
purescript-yoga-json basically provides two functions writeJSON and readJSON.
Use writeJSON to serialise a type to a JSON string:
import Yoga.JSON as JSON
serialised :: String
serialised =
JSON.writeJSON { first_name: "Lola", last_name: "Flores" }
-- {"last_name":"Flores","first_name":"Lola"}Use readJSON to deserialise a JSON string:
import Yoga.JSON as JSON
deserialised :: Either MultipleErrors { first_name :: String, last_name :: String }
deserialised = JSON.readJSON """{ "first_name": "Lola", "last_name": "Flores" }"""
-- Right { first_name: "Lola", last_name: "Flores" }As the parsing can fail, readJSON returns an Either, potentially containing a Left data constructor with MultipleErrors. If you don't care about the specific errors, you can use readJSON_, which returns a Maybe:
deserialised :: Maybe { first_name :: String, last_name :: String }
deserialised = JSON.readJSON_ """{ "first_name": "Lola", "last_name": "Flores" }"""
-- Just { first_name: "Lola", last_name: "Flores" }purescript-yoga-json provides utility functions to easily generate serialisers and deserialisers for your sum types.
Let's start with a simple example of a sum type where our data constructors do not contain any further values:
import Data.Either (Either)
import Data.Generic.Rep (class Generic)
import Yoga.JSON as JSON
import Yoga.JSON.Generics (genericReadForeignEnum, genericWriteForeignEnum)
import Yoga.JSON.Generics.EnumSumRep as Enum
data MyEnum = Enum1 | Enum2 | Enum3
Now, we need to derive a Generic instance for it:
derive instance Generic MyEnum _
-- and optionally a Show instance
instance Show MyEnum where
show = genericShow
Next, we define a WriteForeign instance and implement the writeImpl function using genericWriteForeignEnum Enum.defaultOptions.
instance WriteForeign MyEnum
where
writeImpl = genericWriteForeignEnum Enum.defaultOptionsSimilarly, we implement the ReadForeign instance using genericReadForeignEnum Enum.defaultOptions
instance ReadForeign MyEnum
where
readImpl = genericReadForeignEnum Enum.defaultOptionsThat's all, we can now serialise our data type:
serialised = writeJSON { "myEnum": Enum3 }
-- {"myEnum":"Enum3"}and deserialise it:
deserialised :: E { "myEnum" :: MyEnum }
deserialised = readJSON serialised
-- Right { myEnum: Enum3 }In order to write your own, custom codecs you will need to provide instances for WriteForeign and ReadForeign.
Let's see an example. We define a simple data type TrafficLight that has three data constructors Red, Yellow and Green:
data TrafficLight = Red | Yellow | GreenWe would like to serialise these constructors as lower case strings "red", "yellow" and "green".
First we need to provide an implementation for the WriteForeign type class, that tells yoga-json how to serialise the type. We pattern match on the three different data constructors and write them as the three Strings red, yellow and green, using the same writeImpl function:
instance WriteForeign TrafficLight where
writeImpl Red = writeImpl "red"
writeImpl Yellow = writeImpl "yellow"
writeImpl Green = writeImpl "green"This works, because yoga-json already knows how to serialise a String.
Similarly, we need to provide an implementation for the ReadForeign type class, that tells yoga-json how to deserialise the type. We start by deserialising into a primitive type, typically a String or an Object, and then convert it to our desired Purescript type.
Since we don't know the input String, it might contain invalid data and therefore deserialisation might fail. yoga-json uses the ExceptT monad to reflect this. We can return valid values using pure and fail on invalid values:
instance ReadForeign TrafficLight where
readImpl json = do
-- deserialise into a string
str :: String <- readImpl json
-- now we pattern match on our valid types
case str of
"red" -> pure Red
"yellow" -> pure Yellow
"green" -> pure Green
-- and fail if we get an invalid type
other -> fail $ ForeignError $ "Failed to parse " <> other <> " as TrafficLight"Now we can serialise our data type:
serialised = writeJSON { "trafficLight": Red }
-- {"trafficLight":"red"}and deserialise it:
deserialised :: E { "trafficLight" :: TrafficLight }
deserialised = readJSON """{ "trafficLight": "green" }"""
-- Right { trafficLight: Green }
deserialisedUnknown :: E { "trafficLight" :: TrafficLight }
deserialisedUnknown = readJSON """{ "trafficLight": "purple" }"""
-- Left (NonEmptyList (NonEmpty (ErrorAtProperty "trafficLight" (ForeignError "Failed to parse purple as TrafficLight")) Nil))