i'm using this function and need to pass it an Aeson Value
:
{ logLevel : vega.Debug }
this is supposed to refer to an enum in a javascript package that the binding doesn't export.
afaict i'm supposed to use Data.Aeson.QQ.Simple
for this, but everything i try that compiles puts quotes around "vega.Debug"
, which i can't have.
[aesonQQ| { logLevel : "vega.Debug" } |]
what am i missing? is there a way to use encode for this?
In general, Aeson Value
s represent JSON objects only, so they don’t support embedded JavaScript expressions, or any other extensions.
If this API only accepts Value
s, you’re stuck. I think the best solution is to just duplicate the integer value of vega.Debug
and serialise that.
Otherwise, a straightforward solution is to make a modified version of toHtmlWith
that accepts a more flexible input type, such as a string:
toHtmlWith' :: Maybe Text -> VegaLite -> Text
toHtmlWith' mopts vl =
let spec = encodeToLazyText (fromVL vl)
-- NB: Removed ‘encodeToLazyText’ call here.
opts = maybe "" (\o -> "," <> o) mopts
in TL.unlines
[ "<!DOCTYPE html>"
, "<html>"
, "<head>"
-- versions are fixed at vega 5, vega-lite 4
, " <script src=\"https://cdn.jsdelivr.net/npm/vega@5\"></script>"
, " <script src=\"https://cdn.jsdelivr.net/npm/vega-lite@4\"></script>"
, " <script src=\"https://cdn.jsdelivr.net/npm/vega-embed\"></script>"
, "</head>"
, "<body>"
, "<div id=\"vis\"></div>"
, "<script type=\"text/javascript\">"
, " var spec = " <> spec <> ";"
, " vegaEmbed(\'#vis\', spec" <> opts <> ").then(function(result) {"
, " // Access the Vega view instance (https://vega.github.io/vega/docs/api/view/) as result.view"
, " }).catch(console.error);"
, "</script>"
, "</body>"
, "</html>"
]
Then you can call encodeToLazyText
on your own Aeson values, or include arbitrary Text
strings as needed.
If you really want to avoid duplicating the page contents, then you could also call the existing toHtmlWith
with a Value
containing a special delimiter that you control, such as String "<user1441998>vega.Debug</user1441998>"
, and then use that delimiter to postprocess the result:
unquoteHackSplices = replace "\"<user1441998>" ""
. replace "</user1441998>\"" ""
is there a way to use encode for this?
As yet another hack, you could make a ToJSON
instance for your type that implements toEncoding
but not toJSON
, and have the encoded value be a JavaScript expression (i.e. invalid JSON). You would want to make toJSON
raise an error so you don’t use it inadvertently.
If you want to generate JavaScript code in general, I would have a look at language-javascript
. Instead of producing a Value
, produce a JSExpression
and then use one of the pretty-printing functions like renderToText
to render it. Here’s a sketch of the structure of a possible solution:
-- Like ‘ToJSON’ but may produce arbitrary JavaScript expressions
class ToJavaScript a where
toJavaScript :: a -> JSExpression
-- Helper function to convert from Aeson Value
jsFromJson :: Value -> JSExpression
jsFromJson v = case v of
Object o -> JSObjectLiteral …
Array a -> JSArrayLiteral …
String s -> JSStringLiteral …
…
instance ToJavaScript YourType where
toJavaScript = …
rendered :: Text
rendered = renderToText
$ JSAstExpression (toJavaScript yourValue) JSNoAnnot
Your expression would have the form:
JSMemberDot
(JSIdentifier JSNoAnnot "vega")
JSNoAnnot
(JSIdentifier JSNoAnnot "Debug")
The JSAnnot
type would also allow you to include comments in the generated result. Bear in mind that the language-javascript
pretty-printing is likely less well optimised than Aeson’s JSON serialisation.