Search code examples
haskellhaskell-lensaeson

Extract the Text from a JSON value String Text without pattern matching


Here is the definition for a Json Value :

-- | A JSON value represented as a Haskell value.
data Value = Object !Object
           | Array !Array
           | String !Text
           | Number !Scientific
           | Bool !Bool
           | Null
             deriving (Eq, Show)

let value = String "myValue"
looking for => fromString value == "myValue" ??
fromString :: Value -> Text

I'm looking a function like where I could get the Text from String without to do some pattern matching, obviously this function will be unsafe... a fromString like fromJust in Data.Maybe for example... Something in Data.Lens.Aeson ?


Solution

  • As Thomas M. DuBuisson implies in the above comment, this sounds like an XY Problem. Still, I'll do my best to nonetheless address the specifics.

    Technically, you can trivially write a function with the type Value -> Text, although it requires pattern matching. I realise that the OP requests a function without pattern matching, but please read on:

    -- Warning: UNSAFE!
    fromString :: Value -> Text
    fromString (String s) = s
    

    Such a function compiles, but is unsafe!

    *Q53961314 Q53961314> fromString $ String "myValue"
    "myValue"
    *Q53961314 Q53961314> fromString $ Number 42
    "*** Exception: Non-exhaustive patterns in function fromString
    

    While it works for String values, it crashes for any other type of value. While it's technically possible to write and compile unsafe functions like the above, AFAICT it's not considered idiomatic Haskell.

    A better alternative is a safe function that returns Maybe Text. This is still easy to write using pattern matching:

    fromStringSafe :: Value -> Maybe Text
    fromStringSafe (String s) = Just s
    fromStringSafe _ = Nothing
    

    This function is total:

    *Q53961314 Q53961314> fromStringSafe $ String "myValue"
    Just "myValue"
    *Q53961314 Q53961314> fromStringSafe $ Number 42
    Nothing
    

    If you don't wish to write such a function yourself, but prefer to use lens-aeson, you can use the _String prism:

    Prelude Data.Aeson Data.Aeson.Lens Control.Lens> String "myValue" ^? _String
    Just "myValue"
    

    As you can tell, this is also safe, in that ^? _String returns Maybe Text:

    Prelude Data.Aeson Data.Aeson.Lens Control.Lens> Bool True ^? _String
    Nothing
    Prelude Data.Aeson Data.Aeson.Lens Control.Lens> Number 42 ^? _String
    Nothing
    

    If you really, really want an unsafe function, you can use ^?! _String:

    Prelude Data.Aeson Data.Aeson.Lens Control.Lens> String "myValue" ^?! _String
    "myValue"
    

    It is, not surprisingly, unsafe:

    Prelude Data.Aeson Data.Aeson.Lens Control.Lens> Number 42 ^?! _String
    "*** Exception: (^?!): empty Fold
    CallStack (from HasCallStack):
      error, called at src\\Control\\Lens\\Fold.hs:1285:28 in lens-4.17-7m3etWEBj0P3172qv7LEG9:Control.Lens.Fold
      ^?!, called at <interactive>:9:1 in interactive:Ghci3
    

    I wonder why you're asking about this sort of functionality, though. Is there a specific problem you're trying to solve with Aeson that we can help with?