haskell-waargonaut / waargonaut

JSON decoding/encoding/manipulation library.
Other
94 stars 14 forks source link

Help: Prettier #62

Open treffynnon opened 5 years ago

treffynnon commented 5 years ago

OK, so there are no tests here to cheat from and I found if I passed in an Int to the Prettier functions (as the docs currently read to me) they'd both reject it.

This is what I've come up with to get it working, but I figure I've probably taken a detour somewhere:

import qualified Waargonaut.Encode as WE
import qualified Waargonaut.Prettier as WEP
import qualified Waargonaut.Types as WT
import qualified Data.Text.Lazy as TL

encode :: [J.Schema] -> [TL.Text]
encode xs =
    let spaces = WEP.NumSpaces $ successor' $ successor' zero' -- this is fun with Natural instead of 2::Int
        indent = WEP.IndentStep $ successor' $ successor' zero'
        beautify = WEP.simpleEncodePretty WEP.ArrayOnly indent spaces WE.json'
        json = WE.asJson' encodeSchema <$> xs
    in runIdentity . beautify <$> json

To get a decent print out in GHCI I am then calling it with:

import qualified Data.Text.Lazy.IO as T

main :: IO ()
main = do
  params <- -- fetches data here of type J.Schema
  foldr (const . T.putStr) (pure ()) $ JS.encode params

J.Schema looks like this:

data Schema = Schema {
  id :: Text,
  schema :: Text,
  schemaDescription :: Text,
  schemaProperties :: Properties
} deriving (Show)

What's the best way of utilising the Prettier stuff and how does one overcome the Natural impediment?

mankyKitty commented 5 years ago

Natural isn't an impediment, I'm using the type system to prevent negative indentation values. The actual issue is that the docs have fallen out of step with the type signature, but that's an easy fix.

If the Waargonaut.Prettier module re-exported the successor' and zero' functions, that might simplify things too.

The Prettier module isn't as well battle-tested as the rest of the library so it may still be a bit gnarly to use. But you're pretty much on the money, the thing I would point out is that you can pass the encodeSchema directly to the simpleEncodePretty function. As it takes an Encoder, it's the prettyJson function that is limited to only Json input.

Also rather than : foldr (const . T.putStr) (pure ()), you can use traverse_:

traverse_ putStr :: Foldable t => t String -> IO ()

foldr (const . putStr) (pure ()) :: Foldable t => t String -> IO ()

I wrote up a small example based on your type:

{-# LANGUAGE OverloadedStrings #-}
module Main where

import           Data.Functor.Identity (runIdentity)

import           Data.Scientific       (Scientific)
import           Natural               (Natural, successor', zero')

import           Data.Text             (Text)
import qualified Data.Text.Lazy.IO     as TIO

import qualified Waargonaut.Encode     as E

import qualified Waargonaut.Prettier   as WEP
import qualified Waargonaut.Types      as WT

data Properties = Properties
  { _propA :: Scientific
  , _propB :: Bool
  }

encodeProperties :: Applicative f => E.Encoder f Properties
encodeProperties = E.mapLikeObj $ \p ->
  E.atKey' "propA" E.scientific (_propA p) .
  E.atKey' "propB" E.bool (_propB p)

data Schema = Schema
  { _id                :: Text
  , _schema            :: Text
  , _schemaDescription :: Text
  , _schemaProperties  :: Properties
  }

encodeSchema :: Monad f => E.Encoder f Schema
encodeSchema = E.keyValuesAsObj
  [ E.encAt E.text "id" _id
  , E.encAt E.text "schema" _schema
  , E.encAt E.text "schemaDescription" _schemaDescription
  , E.encAt encodeProperties  "schemaProperties" _schemaProperties
  ]

main :: IO ()
main =
  let
    aSchema = Schema "raqxpn9rvfsdk" "Fred" "Turtle" (Properties 3.149e-6 False)

    -- It is fun when the type system prevents invalid states!
    two = successor' $ successor' zero'

    makePretty = runIdentity . WEP.simpleEncodePretty
      WEP.ArrayOnly
      (WEP.IndentStep two)
      (WEP.NumSpaces two)
      encodeSchema

  in
    TIO.putStrLn (makePretty aSchema)

This will print this output:

{
  "id":                "TACO",
  "schema":            "Fred",
  "schemaDescription": "Turtle",
  "schemaProperties":  {
    "propA": 3.149e-6,
    "propB": false
  }
}
treffynnon commented 5 years ago

Thank you for the clarification. I understand what the Natural type is there to do now and can see why it is used. I'd just never encountered it before and found it confounding when I was shoving Ints at Prettier - should've just followed the types :) As it seemed a bit convoluted I assumed I was making a rookie mistake by building two that way.

The example code you've included is very informative - I was going to make a pull request adding an example of using atKey' with maybeOrNull and scientific - probably not needed now!

mankyKitty commented 5 years ago

That's understandable, I think we're all a bit complacent when it comes to use of values that are "just a number", when in fact those numbers have a purpose and often have properties that we should and could enforce!

Please submit that PR! Examples and documentation are always useful and it works best when it's not from only one person. :)