packages feed

fb-util-0.1.0.1: Util/JSON/Pretty.hs

{-
  Copyright (c) Meta Platforms, Inc. and affiliates.
  All rights reserved.

  This source code is licensed under the BSD-style license found in the
  LICENSE file in the root directory of this source tree.
-}

{-# OPTIONS -Wno-orphans #-}

-- | Display JSON values using pretty printing combinators.
--
-- Adapted from Text.Pretty.JSON in the json package, which is
--   Copyright (c) Galois, Inc. 2007
--
-- Why would you use this instead of Aeson + aeson-pretty?
--   1. You want to retain the ordering of fields in JSON objects. Aeson
--      doesn't do this.
--   2. The pretty-printer here produces more compact output than aeson-pretty.

module Util.JSON.Pretty
  ( -- instance Pretty JSValue
  ) where

import Data.Ratio
import Data.Char
import Numeric
import Text.JSON

import Compat.Prettyprinter

instance Pretty JSValue where
  pretty v = case v of
    JSNull -> "null"
    JSBool True -> "true"
    JSBool False -> "false"
    JSRational asf x -> pp_number asf x
    JSString x -> pp_string (fromJSString x)
    JSArray vs -> pp_array vs
    JSObject xs -> pp_object (fromJSObject xs)

pp_number :: Bool -> Rational -> Doc a
pp_number _ x | denominator x == 1 = pretty (numerator x)
pp_number True x = pretty (fromRational x :: Float)
pp_number _ x = pretty (fromRational x :: Double)

pp_array :: [JSValue] -> Doc a
pp_array xs = sep [nest 2 (vsep ("[" : punctuate comma (map pretty xs))), "]"]

pp_object :: [(String,JSValue)] -> Doc a
pp_object xs =
  sep [nest 2 (vsep ("{" : punctuate comma (map pp_field xs))), "}"]
  where pp_field (k,v) = pp_string k <> colon <+> pretty v

pp_string :: String -> Doc a
pp_string x = dquotes $ hcat $ map pp_char x
  where pp_char '\\'            = "\\\\"
        pp_char '"'             = "\\\""
        pp_char c | isControl c = uni_esc c
        pp_char c               = pretty [c]

        uni_esc c = "\\u" <> pretty (pad 4 (showHex (fromEnum c) ""))

        pad n cs  | len < n   = replicate (n-len) '0' ++ cs
                  | otherwise = cs
          where len = length cs