在 URI 上定义 FromJSON

Defining FromJSON on URI

使用Network-URI和Aeson,我定义了以下FROMJson URI

简而言之,我正在尝试实现一个 JSON 解码器,要求:

{ "value" : X } 其中 X 是一个 JSON 字符串,对于 parseURI(X).[=21= 计算结果为 Just ]

instance FromJSON URI where
    parseJSON = withObject "URI" $ \v ->
      case Data.HashMap.Lazy.lookup "value" v of
        Just (String str) -> let unpacked = T.unpack str in
          case parseURI unpacked of
            Just uri -> pure uri
            Nothing  -> fail $ "String:" ++ unpacked ++ "is not a valid URI."
        Just value        -> typeMismatch "value" value
        Nothing           -> fail $ "JSON object does not have 'value' key."

但是,对于有效的 URI,我的代码不起作用:

λ: >parseURI "http://www.foo.com"
Just http://www.foo.com

λ: >toJSON $ URI "http" Nothing "//www.foo.com" "" ""
String "http//www.foo.com"

λ: >fromJSON $ toJSON $ URI "http" Nothing "//www.foo.com" "" ""
Error "expected (), encountered String"

如何修复我的 FromJSON 实例?

您的实例需要一个形状为 { "value": "url" } 的 JSON 对象,但是您使用的 toJSON 实例显然会生成一个纯字符串,正如您的交互式输出清楚地表明的那样。

要使交互式会话中的最后一个表达式起作用,您需要使 ToFrom 实例对称:要么使 ToJSON 实例生成一个对象 { "value": "url" } 或使 FromJSON 实例需要一个纯字符串。

此外,顺便说一句,您的实例可以使用 .: 运算符以更短的方式表达:

instance FromJSON URI where
    parseJSON = withObject "URI" $ \v -> do
        mUri <- parseURI . T.unpack <$> v .: "value"
        maybe (fail "Bad URI") pure mUri