diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,7 @@
 Changelog for Hoogle (* = breaking change)
 
+5.0.17.11, released 2019-08-23
+    #318, allow --json to get results JSON from the command line
 5.0.17.10, released 2019-08-02
     #314, fix bad URL trying to access package on Hoogle with --local
     #311, fix an overflow with weight computations
diff --git a/hoogle.cabal b/hoogle.cabal
--- a/hoogle.cabal
+++ b/hoogle.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.18
 build-type:         Simple
 name:               hoogle
-version:            5.0.17.10
+version:            5.0.17.11
 license:            BSD3
 license-file:       LICENSE
 category:           Development
diff --git a/src/Action/CmdLine.hs b/src/Action/CmdLine.hs
--- a/src/Action/CmdLine.hs
+++ b/src/Action/CmdLine.hs
@@ -22,6 +22,7 @@
 data CmdLine
     = Search
         {color :: Maybe Bool
+        ,json :: Bool
         ,link :: Bool
         ,numbers :: Bool
         ,info :: Bool
@@ -108,6 +109,7 @@
 
 search_ = Search
     {color = def &= name "colour" &= help "Use colored output (requires ANSI terminal)"
+    ,json = def &= name "json" &= help "Get result as JSON"
     ,link = def &= help "Give URL's for each result"
     ,numbers = def &= help "Give counter for each result"
     ,info = def &= help "Give extended information about the first result"
diff --git a/src/Action/Search.hs b/src/Action/Search.hs
--- a/src/Action/Search.hs
+++ b/src/Action/Search.hs
@@ -10,6 +10,7 @@
 import Control.DeepSeq
 import Control.Monad.Extra
 import Control.Exception.Extra
+import qualified Data.Aeson as JSON
 import Data.Functor.Identity
 import Data.List.Extra
 import Text.Blaze.Renderer.Utf8
@@ -47,8 +48,8 @@
                  putStr $ targetInfo $ head res
              else do
                 let toShow = if numbers && not info then addCounter shown else shown
-                putStr $ unlines toShow
-                when (hidden /= []) $ do
+                if json then LBS.putStrLn $ JSON.encode $ map unHTMLtargetItem res else putStr $ unlines toShow
+                when (hidden /= [] && not json) $ do
                     whenNormal $ putStrLn $ "-- plus more results not shown, pass --count=" ++ show (count+10) ++ " to see more"
         else do
             let parseType x = case parseQuery x of
@@ -72,6 +73,9 @@
         [targetItem] ++
         ["-- " ++ targetURL | link]
 
+unHTMLtargetItem :: Target -> Target
+unHTMLtargetItem target = target {targetItem = unHTML $ targetItem target}
+
 addCounter :: [String] -> [String]
 addCounter = zipWith (\i x -> show i ++ ") " ++ x) [1..]
 
@@ -165,7 +169,7 @@
         "Ord a => [a] -> [a]" === hackage "base/docs/Data-List.html#v:sort"
         "ShakeOptions -> Int" === hackage "shake/docs/Development-Shake.html#v:shakeThreads"
         "is:module" === hackage "base/docs/Prelude.html"
-        "visibleDataCons" === hackage "ghc/docs/TyCon.html#v:visibleDataCons"
+        "visibleDataCons" === hackage "ghc-lib-parser/docs/TyCon.html#v:visibleDataCons"
         "sparkle" === hackage "sparkle" -- library without Hoogle docs
         "weeder" === hackage "weeder" -- executable in Stackage
         "supero" === hackage "supero"
diff --git a/src/Action/Server.hs b/src/Action/Server.hs
--- a/src/Action/Server.hs
+++ b/src/Action/Server.hs
@@ -61,9 +61,7 @@
         \x -> BS.pack "hoogle=" `BS.isInfixOf` x && not (BS.pack "is:ping" `BS.isInfixOf` x)
     putStrLn . showDuration =<< time
     evaluate spawned
-    dataDir <- case datadir of
-        Just d -> return d
-        Nothing -> getDataDir
+    dataDir <- maybe getDataDir return datadir
     haddock <- maybe (return Nothing) (fmap Just . canonicalizePath) haddock
     withSearch database $ \store ->
         server log cmd $ replyServer log local links haddock store cdn home (dataDir </> "html") scope
diff --git a/src/General/Store.hs b/src/General/Store.hs
--- a/src/General/Store.hs
+++ b/src/General/Store.hs
@@ -118,7 +118,7 @@
         -- sort the atoms and validate there are no duplicates
         let atoms = Map.fromList swAtoms
         when (Map.size atoms /= length swAtoms) $
-            errorIO "Some duplicate names have been written out"
+            errorIO $ "Some duplicate names have been written out: " ++ show (length swAtoms - Map.size atoms) ++ " duplicates"
 
         -- write the atoms out, then put the size at the end
         let bs = encodeBS atoms
diff --git a/src/General/Web.hs b/src/General/Web.hs
--- a/src/General/Web.hs
+++ b/src/General/Web.hs
@@ -46,7 +46,7 @@
               . BS.pack
     badPath = any (all (== '.')) . filter (/= "")
     args = parseArgs b
-    parseArgs = map (\(n, v) -> (BS.unpack n, maybe "" BS.unpack v))
+    parseArgs = map (BS.unpack *** maybe "" BS.unpack)
               . parseQuery
               . BS.pack
     badArgs = any (any (not . isLower))
diff --git a/src/Output/Tags.hs b/src/Output/Tags.hs
--- a/src/Output/Tags.hs
+++ b/src/Output/Tags.hs
@@ -2,6 +2,7 @@
 
 module Output.Tags(writeTags, completionTags, applyTags) where
 
+import Data.Bifunctor
 import Data.Function
 import Data.List.Extra
 import Data.Tuple.Extra
@@ -45,7 +46,7 @@
     let packages = addRange splitPkg
     storeWrite store Packages (bstr0Join $ map (strUnpack . fst) packages, V.fromList $ map snd packages)
 
-    let categories = map (first snd . second reverse) $ Map.toList $ Map.fromListWith (++)
+    let categories = map (bimap snd reverse) $ Map.toList $ Map.fromListWith (++)
             [(((weightTag ex, both lower ex), joinPair ":" ex),[rng]) | (p,rng) <- packages, ex <- extra p]
     storeWrite store Categories (bstr0Join $ map fst categories, jaggedFromList $ map snd categories)
 
