如何使用 Aeson 生成文字(不带引号)javascript 表达式?

How do I use Aeson to generate a literal (unquoted) javascript expression?

我正在使用 this function and need to pass it an Aeson Value:

{ logLevel : vega.Debug }

这应该是指绑定不导出的 javascript 包中的 enum

afaict 我应该为此使用 Data.Aeson.QQ.Simple,但我尝试编译的所有内容都在 "vega.Debug" 周围加上引号,我不能这样做。

[aesonQQ| { logLevel : "vega.Debug" } |]

我错过了什么?有没有办法为此使用 encode

一般来说,Aeson Values 仅表示 JSON 对象,因此它们不支持嵌入的 JavaScript 表达式或任何其他扩展。

如果这个 API 只 接受 Value,你就卡住了。我认为 最好的 解决方案是复制 vega.Debug 的整数值并将其序列化。

否则,一个直接的解决方案是制作 toHtmlWith 的修改版本,它接受更灵活的输入类型,例如字符串:

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>"
    ]

然后您可以根据自己的 Aeson 值调用 encodeToLazyText,或根据需要包含任意 Text 字符串。

如果您真的想避免重复页面内容,那么您也可以使用包含特殊分隔符的 Value 调用现有的 toHtmlWith您控制,例如 String "<user1441998>vega.Debug</user1441998>",然后使用该分隔符对结果进行后处理:

unquoteHackSplices = replace "\"<user1441998>" ""
  . replace "</user1441998>\"" ""

is there a way to use encode for this?

作为另一个 hack,你可以为你的类型创建一个 ToJSON 实例来实现 toEncoding 不是 toJSON,并且有编码值是一个 JavaScript 表达式(即无效的 JSON)。你会想要 toJSON 引发错误,这样你就不会无意中使用它。


如果您想生成 JavaScript 一般代码,我会看一下 language-javascript. Instead of producing a Value, produce a JSExpression and then use one of the pretty-printing functions like renderToText 来渲染它。这是一个可能的解决方案的结构草图:

-- 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

您的表达式将采用以下形式:

JSMemberDot
  (JSIdentifier JSNoAnnot "vega")
  JSNoAnnot
  (JSIdentifier JSNoAnnot "Debug")

JSAnnot 类型还允许您在生成的结果中包含注释。请记住,language-javascript pretty-printing 的优化可能不如 Aeson 的 JSON 序列化。