diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,6 @@
+module Main where
+
+import Cabal2JSON
+
+main :: IO ()
+main = cabal2JSON
diff --git a/cabal2json.cabal b/cabal2json.cabal
new file mode 100644
--- /dev/null
+++ b/cabal2json.cabal
@@ -0,0 +1,74 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.34.7.
+--
+-- see: https://github.com/sol/hpack
+
+name:           cabal2json
+version:        0.0.0.0
+synopsis:       Turn a .cabal file into a .json file
+homepage:       https://github.com/NorfairKing/cabal2json#readme
+bug-reports:    https://github.com/NorfairKing/cabal2json/issues
+author:         Tom Sydney Kerckhove
+maintainer:     syd@cs-syd.eu
+copyright:      Copyright (c) 2022 Tom Sydney Kerckhove
+license:        MIT
+build-type:     Simple
+
+source-repository head
+  type: git
+  location: https://github.com/NorfairKing/cabal2json
+
+library
+  exposed-modules:
+      Cabal2JSON
+  other-modules:
+      Paths_cabal2json
+  hs-source-dirs:
+      src
+  build-depends:
+      Cabal
+    , aeson
+    , autodocodec
+    , base >=4.7 && <5
+    , bytestring
+    , hashable
+    , pretty
+    , pretty-show
+    , text
+    , unordered-containers
+  default-language: Haskell2010
+
+executable cabal2json
+  main-is: Main.hs
+  other-modules:
+      Paths_cabal2json
+  hs-source-dirs:
+      app
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , cabal2json
+  default-language: Haskell2010
+
+test-suite cabal2json-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Cabal2JSONSpec
+      Paths_cabal2json
+  hs-source-dirs:
+      test
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-tool-depends:
+      sydtest-discover:sydtest-discover
+  build-depends:
+      Cabal
+    , autodocodec
+    , autodocodec-yaml
+    , base >=4.7 && <5
+    , cabal2json
+    , sydtest
+    , sydtest-aeson
+    , utf8-string
+  default-language: Haskell2010
diff --git a/src/Cabal2JSON.hs b/src/Cabal2JSON.hs
new file mode 100644
--- /dev/null
+++ b/src/Cabal2JSON.hs
@@ -0,0 +1,788 @@
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedLists #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module Cabal2JSON
+  ( cabal2JSON,
+  )
+where
+
+import Autodocodec
+import Control.Arrow (first)
+import Data.Aeson (FromJSON, ToJSON)
+import qualified Data.ByteString.Char8 as SB8
+import qualified Data.ByteString.Lazy as LB
+import qualified Data.HashMap.Strict as HM
+import Data.Hashable
+import qualified Data.List.NonEmpty as NE
+import qualified Data.Text as T
+import Data.Text.Encoding
+import Distribution.Compiler as Cabal
+import Distribution.License as Cabal
+import Distribution.ModuleName as Cabal
+import Distribution.PackageDescription
+import Distribution.PackageDescription.Parsec as Cabal
+import Distribution.Parsec (eitherParsec)
+import Distribution.Pretty as Pretty
+import Distribution.SPDX (LicenseExpression (..), SimpleLicenseExpression (..))
+import qualified Distribution.SPDX as SPDX
+import Distribution.SPDX.License as SPDX
+import Distribution.System
+import Distribution.Types.CondTree as Cabal
+import Distribution.Types.Dependency as Cabal
+import Distribution.Types.ExeDependency as Cabal
+import Distribution.Types.ExecutableScope as Cabal
+import Distribution.Types.ForeignLib as Cabal
+import Distribution.Types.ForeignLibOption as Cabal
+import Distribution.Types.ForeignLibType as Cabal
+import Distribution.Types.IncludeRenaming as Cabal
+import Distribution.Types.LegacyExeDependency as Cabal
+import Distribution.Types.LibraryVisibility as Cabal
+import Distribution.Types.Mixin as Cabal
+import Distribution.Types.PackageId as Cabal
+import Distribution.Types.PackageName as Cabal
+import Distribution.Types.PkgconfigDependency as Cabal
+import Distribution.Types.PkgconfigName
+import Distribution.Types.PkgconfigVersion
+import Distribution.Types.PkgconfigVersionRange
+import Distribution.Types.UnqualComponentName as Cabal
+import Distribution.Types.Version as Cabal
+import Distribution.Types.VersionRange as Cabal
+import Distribution.Utils.ShortText as Cabal
+import Distribution.Verbosity as Cabal
+import Language.Haskell.Extension
+import System.Environment
+
+cabal2JSON :: IO ()
+cabal2JSON = do
+  args <- getArgs
+  case args of
+    [] -> error "Please provide path to cabal file as CLI argument"
+    arg -> do
+      genericPackageDescription <- Cabal.readGenericPackageDescription Cabal.deafening (head arg)
+      SB8.putStrLn $ LB.toStrict $ encodeJSONViaCodec genericPackageDescription
+
+instance HasCodec GenericPackageDescription where
+  codec =
+    object "GenericPackageDescription" $
+      GenericPackageDescription
+        <$> requiredField' "description" .= packageDescription
+        <*> optionalFieldWithOmittedDefault' "package-flags" [] .= genPackageFlags
+        <*> optionalField' "library" .= condLibrary
+        <*> optionalFieldWithOmittedDefaultWith' "sublibraries" unqualComponentNameCodec [] .= condSubLibraries
+        <*> optionalFieldWithOmittedDefaultWith' "foreign-libs" unqualComponentNameCodec [] .= condForeignLibs
+        <*> optionalFieldWithOmittedDefaultWith' "executables" unqualComponentNameCodec [] .= condExecutables
+        <*> optionalFieldWithOmittedDefaultWith' "test-suites" unqualComponentNameCodec [] .= condTestSuites
+        <*> optionalFieldWithOmittedDefaultWith' "benchmarks" unqualComponentNameCodec [] .= condBenchmarks
+
+deriving via (Autodocodec GenericPackageDescription) instance (FromJSON GenericPackageDescription)
+
+deriving via (Autodocodec GenericPackageDescription) instance (ToJSON GenericPackageDescription)
+
+unqualComponentNameCodec :: HasCodec b => JSONCodec [(UnqualComponentName, b)]
+unqualComponentNameCodec = mapInListForCodec mkUnqualComponentName unUnqualComponentName
+
+instance HasCodec UnqualComponentName where
+  codec = dimapCodec mkUnqualComponentName unUnqualComponentName codec
+
+mapInListForCodec ::
+  HasCodec b =>
+  (String -> a) ->
+  (a -> String) ->
+  JSONCodec [(a, b)]
+mapInListForCodec fromText toText =
+  dimapCodec
+    (f . HM.toList)
+    (HM.fromList . g)
+    codec
+  where
+    f = map (first fromText)
+    g = map (first toText)
+
+instance Hashable UnqualComponentName where
+  hashWithSalt s = hashWithSalt s . unUnqualComponentName
+
+instance HasCodec VersionRange where
+  codec = bimapCodec f g codec
+    where
+      f = \s -> case eitherParsec s of
+        Left pe -> Left $ show pe
+        Right a -> Right a
+      g = prettyShow
+
+instance HasCodec CompilerFlavor where
+  codec =
+    stringConstCodec
+      [ (GHC, "GHC"),
+        (GHCJS, "GHCJS"),
+        (NHC, "NHC"),
+        (YHC, "YHC"),
+        (HBC, "HBC"),
+        (Helium, "Helium"),
+        (JHC, "JHC"),
+        (LHC, "LHC"),
+        (UHC, "UHC"),
+        (Eta, "Eta")
+      ]
+
+instance HasCodec PackageDescription where
+  codec =
+    object "PackageDescription" $
+      PackageDescription
+        <$> requiredField' "spec-version" .= specVersionRaw
+        <*> requiredField' "package" .= package
+        <*> requiredField' "license" .= licenseRaw
+        <*> requiredField' "license-files" .= licenseFiles
+        <*> requiredField' "copyright" .= copyright
+        <*> requiredField' "maintainer" .= maintainer
+        <*> requiredField' "author" .= author
+        <*> requiredField' "stability" .= stability
+        <*> requiredField' "tested-with" .= testedWith
+        <*> requiredField' "homepage" .= homepage
+        <*> requiredField' "pkg-url" .= pkgUrl
+        <*> requiredField' "bug-reports" .= bugReports
+        <*> requiredField' "source-repos" .= sourceRepos
+        <*> requiredField' "synopsis" .= synopsis
+        <*> requiredField' "description" .= description
+        <*> requiredField' "category" .= category
+        <*> requiredField' "custom-fields" .= customFieldsPD
+        <*> requiredField' "build-type-raw" .= buildTypeRaw
+        <*> requiredField' "custom-setup" .= setupBuildInfo
+        <*> requiredField' "library" .= library
+        <*> requiredField' "sublibraries" .= subLibraries
+        <*> requiredField' "executables" .= executables
+        <*> requiredField' "foreign-libs" .= foreignLibs
+        <*> requiredField' "test-suites" .= testSuites
+        <*> requiredField' "benchmarks" .= benchmarks
+        <*> requiredField' "data-files" .= dataFiles
+        <*> requiredField' "data-dir" .= dataDir
+        <*> requiredField' "extra-source-files" .= extraSrcFiles
+        <*> requiredField' "extra-tmp-files" .= extraTmpFiles
+        <*> requiredField' "extra-doc-files" .= extraDocFiles
+
+instance HasCodec (PerCompilerFlavor [String]) where
+  codec = dimapCodec (uncurry PerCompilerFlavor) (\(PerCompilerFlavor a b) -> (a, b)) codec
+
+instance (HasCodec a) => HasCodec (a, a) where
+  codec = dimapCodec (\(a : [b]) -> (a, b)) (\(a, b) -> [a, b]) codec
+
+instance HasCodec (CompilerFlavor, VersionRange) where
+  codec =
+    object "(CompilerFlavor, VersionRange)" $
+      (,)
+        <$> requiredField' "compiler" .= fst
+        <*> requiredField' "versionRange" .= snd
+
+instance (HasCodec a) => HasCodec (a, a, a) where
+  codec = dimapCodec (\(a : b : [c]) -> (a, b, c)) (\(a, b, c) -> [a, b, c]) codec
+
+instance HasCodec (Maybe String, String) where
+  codec =
+    dimapCodec
+      ( \case
+          ("", s) -> (Nothing, s)
+          (s1, s2) -> (Just s1, s2)
+      )
+      ( \case
+          (Nothing, s) -> ("", s)
+          (Just s1, s2) -> (s1, s2)
+      )
+      codec
+
+instance HasCodec PackageName where
+  codec = dimapCodec mkPackageName unPackageName codec
+
+instance HasCodec PackageIdentifier where
+  codec =
+    object "PackgeIdentifier" $
+      PackageIdentifier
+        <$> requiredField' "name" .= pkgName
+        <*> requiredField' "version" .= pkgVersion
+
+instance HasCodec RepoKind where
+  codec =
+    object "RepoKind" $
+      dimapCodec f g $
+        eitherCodec (pure RepoHead) $
+          eitherCodec (pure RepoThis) $
+            requiredField' "repo-kind"
+    where
+      f = \case
+        Left _ -> RepoHead
+        Right (Left _) -> RepoThis
+        Right (Right s) -> RepoKindUnknown s
+      g = \case
+        RepoHead -> Left ()
+        RepoThis -> Right $ Left ()
+        RepoKindUnknown s -> Right $ Right s
+
+instance HasCodec RepoType where
+  -- TODO add OtherRepoType
+  codec =
+    stringConstCodec
+      [ (Darcs, "Darcs"),
+        (Git, "Git"),
+        (SVN, "SVN"),
+        (CVS, "CVS"),
+        (Mercurial, "Mercurial"),
+        (GnuArch, "GnuArch"),
+        (Bazaar, "Bazaar"),
+        (Monotone, "Monotone")
+      ]
+
+instance HasCodec Dependency where
+  codec =
+    object "Dependency" $
+      Dependency
+        <$> requiredField' "package-name" .= (\(Dependency name _ _) -> name)
+        <*> requiredField' "version-range" .= (\(Dependency _ version _) -> version)
+        <*> requiredField' "library" .= (\(Dependency _ _ lib) -> lib)
+
+instance HasCodec SourceRepo where
+  codec =
+    object "SourceRepo" $
+      SourceRepo
+        <$> requiredField' "kind" .= repoKind
+        <*> requiredField' "type" .= repoType
+        <*> requiredField' "location" .= repoLocation
+        <*> requiredField' "module" .= repoModule
+        <*> requiredField' "branch" .= repoBranch
+        <*> requiredField' "tag" .= repoTag
+        <*> requiredField' "subdir" .= repoSubdir
+
+instance HasCodec ShortText where
+  codec = dimapCodec toShortText fromShortText codec
+
+instance HasCodec Cabal.License where
+  -- TODO add others
+  codec =
+    named "Cabal.License" $
+      stringConstCodec
+        [ (BSD2, "BSD2"),
+          (BSD3, "BSD3"),
+          (BSD4, "BSD4"),
+          (MIT, "MIT"),
+          (ISC, "ISC"),
+          (PublicDomain, "PublicDomain"),
+          (AllRightsReserved, "AllRightsReserved"),
+          (UnspecifiedLicense, "UnspecifiedLicense"),
+          (OtherLicense, "OtherLicense")
+        ]
+
+instance HasCodec SPDX.License where
+  codec =
+    named "SPDX.License" $
+      object "spdx-license" $
+        dimapCodec f g $
+          eitherCodec (pure NONE) $
+            requiredField' "license-expression"
+    where
+      f = \case
+        Left _ -> NONE
+        Right e -> License e
+      g = \case
+        NONE -> Left ("None" :: String)
+        License e -> Right e
+
+instance HasCodec SPDX.LicenseExpression where
+  codec =
+    named "LicenseExpression" $
+      dimapCodec f g $
+        eitherCodec (object "ELicense" $ (,) <$> requiredField' "expression" .= fst <*> requiredField' "exception-id" .= snd) $
+          eitherCodec (object "EAnd" $ (,) <$> requiredField' "expression-1" .= fst <*> requiredField' "expression-2" .= snd) $
+            object "EOr" $ (,) <$> requiredField' "expression-11" .= fst <*> requiredField' "expression-22" .= snd
+    where
+      f = \case
+        Left (e, c) -> ELicense e c
+        Right (Left (exp1, exp2)) -> EAnd exp1 exp2
+        Right (Right (exp1, exp2)) -> EOr exp1 exp2
+      g = \case
+        ELicense e c -> Left (e, c)
+        EAnd exp1 exp2 -> Right $ Left (exp1, exp2)
+        EOr exp1 exp2 -> Right $ Right (exp1, exp2)
+
+instance HasCodec SPDX.SimpleLicenseExpression where
+  codec =
+    object "SimpleLicenseExpression" $
+      dimapCodec f g $
+        eitherCodec (requiredField' "id") $
+          eitherCodec (requiredField' "id-plus") $
+            requiredField' "license-ref"
+    where
+      f = \case
+        Left i -> ELicenseId i
+        Right (Left i) -> ELicenseIdPlus i
+        Right (Right ref) -> ELicenseRef ref
+      g = \case
+        ELicenseId i -> Left i
+        ELicenseIdPlus i -> Right $ Left i
+        ELicenseRef ref -> Right $ Right ref
+
+instance HasCodec SPDX.LicenseId where
+  codec = named "SPDX.LicenseId" shownBoundedEnumCodec
+
+instance HasCodec SPDX.LicenseRef where
+  codec = dimapCodec f g codec
+    where
+      f = uncurry SPDX.mkLicenseRef'
+      g = \l -> (SPDX.licenseDocumentRef l, SPDX.licenseRef l)
+
+instance HasCodec SPDX.LicenseExceptionId where
+  codec = shownBoundedEnumCodec
+
+instance HasCodec BuildType where
+  codec = stringConstCodec [(Simple, "Simple"), (Configure, "Configure"), (Make, "Make"), (Custom, "Custom")]
+
+instance HasCodec SetupBuildInfo where
+  codec =
+    object "SetupBuildInfo" $
+      SetupBuildInfo
+        <$> requiredField' "setup-depends" .= setupDepends
+        -- TODO: not needed
+        <*> requiredField' "__defaultSetupDepends" .= defaultSetupDepends
+
+instance HasCodec Flag where
+  codec =
+    object "Flag" $
+      MkFlag
+        <$> requiredField' "name" .= flagName
+        <*> requiredField' "description" .= flagDescription
+        <*> requiredField' "default" .= flagDefault
+        <*> requiredField' "manual" .= flagManual
+
+instance HasCodec FlagName where
+  codec = dimapCodec mkFlagName unFlagName codec
+
+instance HasCodec a => HasCodec (CondTree ConfVar [Dependency] a) where
+  codec =
+    named "ConditionalTree" $
+      object "CondTree" $
+        CondNode
+          <$> requiredField' "condTreeData" .= condTreeData
+          <*> requiredField' "condTreeConstraints" .= condTreeConstraints
+          <*> requiredField' "condTreeComponents" .= condTreeComponents
+
+instance (HasCodec a) => HasCodec (CondBranch ConfVar [Dependency] a) where
+  codec =
+    object "CondBranch" $
+      CondBranch
+        <$> requiredField' "condBranchCondition" .= condBranchCondition
+        <*> requiredField' "condBranchIfTrue" .= condBranchIfTrue
+        <*> optionalField' "condBranchIfFalse" .= condBranchIfFalse
+
+instance HasCodec (Condition ConfVar) where
+  codec =
+    named "Condition" $
+      dimapCodec f g $
+        eitherCodec (object "var" $ requiredField' "v") $
+          eitherCodec (object "lit" $ requiredField' "bool") $
+            eitherCodec (object "cnot" $ requiredField' "cond") $
+              eitherCodec (object "cor" $ (,) <$> requiredField' "cond1" .= fst <*> requiredField' "cond2" .= snd) $
+                object "cand" $ (,) <$> requiredField' "cond12" .= fst <*> requiredField' "cond22" .= snd
+    where
+      f = \case
+        Left c -> Var c
+        Right (Left b) -> Lit b
+        Right (Right (Left cond)) -> CNot cond
+        Right (Right (Right (Left (cond1, cond2)))) -> COr cond1 cond2
+        Right (Right (Right (Right (cond1, cond2)))) -> CAnd cond1 cond2
+      g = \case
+        Var c -> Left c
+        Lit b -> Right $ Left b
+        CNot cond -> Right $ Right $ Left cond
+        COr cond1 cond2 -> Right $ Right $ Right $ Left (cond1, cond2)
+        CAnd cond1 cond2 -> Right $ Right $ Right $ Right (cond1, cond2)
+
+instance HasCodec ConfVar where
+  codec =
+    object "ConfVar" $
+      dimapCodec f g $
+        eitherCodec (requiredField' "os") $
+          eitherCodec (requiredField' "arch") $
+            eitherCodec (requiredField' "flag") $
+              (,) <$> requiredField' "compiler" .= fst <*> requiredField' "version" .= snd
+    where
+      f = \case
+        Left o -> OS o
+        Right (Left a) -> Arch a
+        Right (Right (Left name)) -> Flag name
+        Right (Right (Right (compiler, version))) -> Impl compiler version
+      g = \case
+        OS o -> Left o
+        Arch a -> Right $ Left a
+        Flag name -> Right $ Right $ Left name
+        Impl compiler version -> Right $ Right $ Right (compiler, version)
+
+instance HasCodec Arch where
+  -- TODO add OtherArch
+  codec =
+    stringConstCodec
+      [ (I386, "I386"),
+        (X86_64, "X86_64"),
+        (PPC, "PPC"),
+        (PPC64, "PPC64"),
+        (Sparc, "Sparc"),
+        (Arm, "Arm"),
+        (AArch64, "AArch64"),
+        (Mips, "Mips"),
+        (SH, "SH"),
+        (IA64, "IA64"),
+        (S390, "S390"),
+        (Alpha, "Alpha"),
+        (Hppa, "Hppa"),
+        (Rs6000, "Rs6000"),
+        (M68k, "M68k"),
+        (Vax, "Vax"),
+        (JavaScript, "JavaScript")
+      ]
+
+instance HasCodec OS where
+  -- TODO add OtherOS
+  codec =
+    stringConstCodec
+      [ (Linux, "Linux"),
+        (Windows, "Windows"),
+        (OSX, "OSX"),
+        (FreeBSD, "FreeBSD"),
+        (OpenBSD, "OpenBSD"),
+        (NetBSD, "NetBSD"),
+        (DragonFly, "DragonFly"),
+        (Solaris, "Solaris"),
+        (AIX, "AIX"),
+        (HPUX, "HPUX"),
+        (IRIX, "IRIX"),
+        (HaLVM, "HaLVM"),
+        (Hurd, "Hurd"),
+        (IOS, "IOS"),
+        (Android, "Android"),
+        (Ghcjs, "Ghcjs")
+      ]
+
+instance HasCodec Library where
+  codec =
+    object "Library" $
+      Library
+        <$> requiredField' "name" .= libName
+        <*> requiredField' "exposed-modules" .= exposedModules
+        <*> requiredField' "re-exported-modules" .= reexportedModules
+        <*> requiredField' "signatures" .= signatures
+        <*> requiredField' "exposed" .= libExposed
+        <*> requiredField' "visibility" .= libVisibility
+        <*> requiredField' "build-info" .= libBuildInfo
+
+instance HasCodec LibraryName where
+  codec =
+    object "LibraryName" $
+      dimapCodec f g $
+        eitherCodec (pure LMainLibName) $
+          requiredField' "sub-lib-name"
+    where
+      f = \case
+        Left _ -> LMainLibName
+        Right s -> LSubLibName s
+      g = \case
+        LMainLibName -> Left ()
+        LSubLibName s -> Right s
+
+instance HasCodec ModuleName where
+  codec = dimapCodec fromComponents components codec
+
+instance HasCodec ModuleReexport where
+  codec =
+    object "ModuleReexport" $
+      ModuleReexport
+        <$> requiredField' "moduleReexportOriginalPackage" .= moduleReexportOriginalPackage
+        <*> requiredField' "moduleReexportOriginalName" .= moduleReexportOriginalName
+        <*> requiredField' "moduleReexportName" .= moduleReexportName
+
+instance HasCodec LibraryVisibility where
+  codec = stringConstCodec [(LibraryVisibilityPublic, "LibraryVisibilityPublic"), (LibraryVisibilityPrivate, "LibraryVisibilityPrivate")]
+
+instance HasCodec ForeignLib where
+  codec =
+    object "ForeignLib" $
+      ForeignLib
+        <$> requiredField' "name" .= foreignLibName
+        <*> requiredField' "type" .= foreignLibType
+        <*> requiredField' "options" .= foreignLibOptions
+        <*> requiredField' "build-info" .= foreignLibBuildInfo
+        <*> requiredField' "version-info" .= foreignLibVersionInfo
+        <*> requiredField' "linux-version" .= foreignLibVersionLinux
+        <*> requiredField' "mod-def-files" .= foreignLibModDefFile
+
+instance HasCodec ForeignLibType where
+  codec = stringConstCodec [(ForeignLibNativeShared, "ForeignLibNativeShared"), (ForeignLibNativeStatic, "ForeignLibNativeStatic"), (ForeignLibTypeUnknown, "ForeignLibTypeUnknown")]
+
+instance HasCodec ForeignLibOption where
+  codec = literalTextValueCodec ForeignLibStandalone "ForeignLibStandalone"
+
+instance HasCodec LibVersionInfo where
+  codec = dimapCodec mkLibVersionInfo libVersionInfoCRA codec
+
+instance HasCodec Version where
+  codec = dimapCodec mkVersion versionNumbers codec
+
+instance HasCodec Executable where
+  codec =
+    object "Executable" $
+      Executable
+        <$> requiredField' "name" .= exeName
+        <*> requiredField' "module-path" .= modulePath
+        <*> requiredField' "scope" .= exeScope
+        <*> requiredField' "build-info" .= buildInfo
+
+instance HasCodec ExecutableScope where
+  codec =
+    stringConstCodec $
+      NE.fromList
+        [ (ExecutablePublic, "public"),
+          (ExecutablePrivate, "private")
+        ]
+
+instance HasCodec TestSuite where
+  codec =
+    object "TestSuite" $
+      TestSuite
+        <$> requiredField' "name" .= testName
+        <*> requiredField' "interface" .= testInterface
+        <*> requiredField' "build-info" .= testBuildInfo
+
+instance HasCodec TestSuiteInterface where
+  codec =
+    object "TestSuiteInterface" $
+      dimapCodec f g $
+        eitherCodec ((,) <$> requiredField' "version" .= fst <*> requiredField' "filepath" .= snd) $
+          eitherCodec ((,) <$> requiredField' "version" .= fst <*> requiredField' "module-name" .= snd) $
+            requiredField' "type"
+    where
+      f = \case
+        Left (v, fp) -> TestSuiteExeV10 v fp
+        Right (Left (v, mn)) -> TestSuiteLibV09 v mn
+        Right (Right tt) -> TestSuiteUnsupported tt
+      g = \case
+        TestSuiteExeV10 v fp -> Left (v, fp)
+        TestSuiteLibV09 v mn -> Right $ Left (v, mn)
+        TestSuiteUnsupported tt -> Right $ Right tt
+
+instance HasCodec TestType where
+  codec =
+    object "TestType" $
+      dimapCodec f g $
+        eitherCodec (requiredField' "version") $
+          eitherCodec (requiredField' "version") $
+            (,) <$> requiredField' "name" .= fst <*> requiredField' "version" .= snd
+    where
+      f = \case
+        Left v -> TestTypeExe v
+        Right (Left v) -> TestTypeLib v
+        Right (Right (s, v)) -> TestTypeUnknown s v
+      g = \case
+        TestTypeExe v -> Left v
+        TestTypeLib v -> Right $ Left v
+        TestTypeUnknown s v -> Right $ Right (s, v)
+
+instance HasCodec Benchmark where
+  codec =
+    object "Benchmark" $
+      Benchmark
+        <$> requiredField' "name" .= benchmarkName
+        <*> requiredField' "interface" .= benchmarkInterface
+        <*> requiredField' "build-info" .= benchmarkBuildInfo
+
+instance HasCodec BenchmarkType where
+  codec =
+    object "BenchmarkType" $
+      dimapCodec f g $
+        eitherCodec (requiredField' "benchmark-type") $
+          (,)
+            <$> requiredField' "type" .= fst
+            <*> requiredField' "version" .= snd
+    where
+      f = \case
+        Left v -> BenchmarkTypeExe v
+        Right (s, v) -> BenchmarkTypeUnknown s v
+      g = \case
+        BenchmarkTypeExe v -> Left v
+        BenchmarkTypeUnknown s v -> Right (s, v)
+
+instance HasCodec BenchmarkInterface where
+  codec =
+    dimapCodec f g $
+      eitherCodec
+        ( object "BenchmarkExeV10" $
+            (,)
+              <$> requiredField' "version" .= fst
+              <*> requiredField' "filepath" .= snd
+        )
+        $ object "BenchmarkUnsupported" $ requiredField' "benchmark-type"
+    where
+      f = \case
+        Left (version, file) -> BenchmarkExeV10 version file
+        Right t -> BenchmarkUnsupported t
+      g = \case
+        BenchmarkExeV10 version file -> Left (version, file)
+        BenchmarkUnsupported t -> Right t
+
+instance HasCodec ModuleRenaming where
+  codec =
+    object "ModuleRenaming" $
+      dimapCodec f g $
+        eitherCodec (pure DefaultRenaming) $
+          eitherCodec (requiredField' "name") (requiredField' "name")
+    where
+      f = \case
+        Left _ -> DefaultRenaming
+        Right (Left m) -> ModuleRenaming m
+        Right (Right m) -> HidingRenaming m
+      g = \case
+        DefaultRenaming -> Left ()
+        ModuleRenaming m -> Right $ Left m
+        HidingRenaming m -> Right $ Right m
+
+instance HasCodec IncludeRenaming where
+  codec =
+    object "IncludeRenaming" $
+      IncludeRenaming
+        <$> requiredField' "includeProvidesRn" .= includeProvidesRn
+        <*> requiredField' "includeRequiresRn" .= includeRequiresRn
+
+instance HasCodec Mixin where
+  codec =
+    object "Mixin" $
+      Mixin
+        <$> requiredField' "package-name" .= mixinPackageName
+        <*> requiredField' "include-renaming" .= mixinIncludeRenaming
+
+instance HasCodec LegacyExeDependency where
+  codec =
+    object "LegacyExeDependency" $
+      LegacyExeDependency
+        <$> requiredField' "package-name" .= (\(LegacyExeDependency s _) -> s)
+        <*> requiredField' "include-renaming" .= (\(LegacyExeDependency _ vr) -> vr)
+
+instance HasCodec ExeDependency where
+  codec =
+    object "ExeDependency" $
+      ExeDependency
+        <$> requiredField' "package-name" .= (\(ExeDependency a _ _) -> a)
+        <*> requiredField' "executable-component-name" .= (\(ExeDependency _ b _) -> b)
+        <*> requiredField' "version-range" .= (\(ExeDependency _ _ c) -> c)
+
+instance HasCodec PkgconfigDependency where
+  codec =
+    object "PkgconfigDependency" $
+      PkgconfigDependency
+        <$> requiredField' "pkgconfigName" .= (\(PkgconfigDependency name _) -> name)
+        <*> requiredField' "pkgconfigVersionRange" .= (\(PkgconfigDependency _ version) -> version)
+
+instance HasCodec PkgconfigName where
+  codec = dimapCodec mkPkgconfigName unPkgconfigName codec
+
+instance HasCodec PkgconfigVersionRange where
+  codec = bimapCodec f g codec
+    where
+      f = \s -> case eitherParsec s of
+        Left pe -> Left $ show pe
+        Right a -> Right a
+      g = prettyShow
+
+instance HasCodec PkgconfigVersion where
+  codec =
+    bimapCodec
+      (Right . PkgconfigVersion . encodeUtf8)
+      ( \(PkgconfigVersion v) -> case decodeUtf8' v of
+          Left ex -> T.pack $ show ex
+          Right txt -> txt
+      )
+      codec
+
+instance HasCodec Language where
+  codec =
+    dimapCodec f g $
+      eitherCodec (literalTextValueCodec Haskell98 "Haskell98") $
+        eitherCodec
+          (literalTextValueCodec Haskell2010 "Haskell2010")
+          codec
+    where
+      f = \case
+        Left l -> l
+        Right (Left l) -> l
+        Right (Right s) -> UnknownLanguage s
+      g = \case
+        Haskell98 -> Left Haskell98
+        Haskell2010 -> Right $ Left Haskell2010
+        UnknownLanguage s -> Right $ Right s
+
+instance HasCodec Extension where
+  codec =
+    named "Extension" $
+      object "Extension" $
+        dimapCodec f g $
+          eitherCodec (requiredField' "enable-extension") $
+            eitherCodec
+              (requiredField' "disable-extension")
+              (requiredField' "unknown-extension")
+    where
+      f = \case
+        Left e -> EnableExtension e
+        Right (Left e) -> DisableExtension e
+        Right (Right s) -> UnknownExtension s
+      g = \case
+        EnableExtension e -> Left e
+        DisableExtension e -> Right $ Left e
+        UnknownExtension e -> Right $ Right e
+
+instance HasCodec KnownExtension where
+  codec = named "KnownExtension" shownBoundedEnumCodec
+
+instance HasCodec BuildInfo where
+  codec =
+    object "BuildInfo" $
+      BuildInfo
+        <$> requiredField' "buildable" .= buildable
+        <*> requiredField' "buildTools" .= buildTools
+        <*> requiredField' "buildToolDepends" .= buildToolDepends
+        <*> requiredField' "cppOptions" .= cppOptions
+        <*> requiredField' "asmOptions" .= asmOptions
+        <*> requiredField' "cmmOptions" .= cmmOptions
+        <*> requiredField' "ccOptions" .= ccOptions
+        <*> requiredField' "cxxOptions" .= cxxOptions
+        <*> requiredField' "ldOptions" .= ldOptions
+        <*> requiredField' "pkgconfigDepends" .= pkgconfigDepends
+        <*> requiredField' "frameworks" .= frameworks
+        <*> requiredField' "extraFrameworkDirs" .= extraFrameworkDirs
+        <*> requiredField' "asmSources" .= asmSources
+        <*> requiredField' "cmmSources" .= cmmSources
+        <*> requiredField' "cSources" .= cSources
+        <*> requiredField' "cxxSources" .= cxxSources
+        <*> requiredField' "jsSources" .= jsSources
+        <*> requiredField' "hsSourceDirs" .= hsSourceDirs
+        <*> requiredField' "otherModules" .= otherModules
+        <*> requiredField' "virtualModules" .= virtualModules
+        <*> requiredField' "autogenModules" .= autogenModules
+        <*> requiredField' "defaultLanguage" .= defaultLanguage
+        <*> requiredField' "otherLanguages" .= otherLanguages
+        <*> requiredField' "defaultExtensions" .= defaultExtensions
+        <*> requiredField' "otherExtensions" .= otherExtensions
+        <*> requiredField' "oldExtensions" .= oldExtensions
+        <*> requiredField' "extraLibs" .= extraLibs
+        <*> requiredField' "extraGHCiLibs" .= extraGHCiLibs
+        <*> requiredField' "extraBundledLibs" .= extraBundledLibs
+        <*> requiredField' "extraLibFlavours" .= extraLibFlavours
+        <*> requiredField' "extraDynLibFlavours" .= extraDynLibFlavours
+        <*> requiredField' "extraLibDirs" .= extraLibDirs
+        <*> requiredField' "includeDirs" .= includeDirs
+        <*> requiredField' "includes" .= includes
+        <*> requiredField' "autogenIncludes" .= autogenIncludes
+        <*> requiredField' "installIncludes" .= installIncludes
+        <*> requiredField' "options" .= options
+        <*> requiredField' "profOptions" .= profOptions
+        <*> requiredField' "sharedOptions" .= sharedOptions
+        <*> requiredField' "staticOptions" .= staticOptions
+        <*> requiredField' "customFieldsBI" .= customFieldsBI
+        <*> requiredField' "targetBuildDepends" .= targetBuildDepends
+        <*> requiredField' "mixins" .= mixins
diff --git a/test/Cabal2JSONSpec.hs b/test/Cabal2JSONSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Cabal2JSONSpec.hs
@@ -0,0 +1,97 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Cabal2JSONSpec (spec) where
+
+import Autodocodec
+import Autodocodec.Yaml
+import Cabal2JSON ()
+import qualified Distribution.Make as Cabal
+import Distribution.ModuleName as Cabal
+import Distribution.PackageDescription.Parsec as Cabal
+import qualified Distribution.SPDX as SPDX
+import Distribution.Types.Benchmark as Cabal
+import Distribution.Types.BenchmarkInterface as Cabal
+import Distribution.Types.BenchmarkType as Cabal
+import Distribution.Types.BuildInfo as Cabal
+import Distribution.Types.BuildType as Cabal
+import Distribution.Types.Executable as Cabal
+import Distribution.Types.ExecutableScope as Cabal
+import Distribution.Types.Flag as Cabal
+import Distribution.Types.ForeignLib as Cabal
+import Distribution.Types.ForeignLibOption as Cabal
+import Distribution.Types.ForeignLibType as Cabal
+import Distribution.Types.GenericPackageDescription as Cabal
+import Distribution.Types.Library as Cabal
+import Distribution.Types.LibraryName as Cabal
+import Distribution.Types.LibraryVisibility as Cabal
+import Distribution.Types.ModuleReexport as Cabal
+import Distribution.Types.PackageDescription as Cabal
+import Distribution.Types.PkgconfigVersion as Cabal
+import Distribution.Types.PkgconfigVersionRange as Cabal
+import Distribution.Types.SourceRepo as Cabal
+import Distribution.Types.TestSuite as Cabal
+import Distribution.Types.TestSuiteInterface as Cabal
+import Distribution.Types.UnqualComponentName as Cabal
+import Distribution.Types.VersionRange as Cabal
+import Distribution.Utils.ShortText as Cabal
+import Distribution.Verbosity as Cabal
+import Language.Haskell.Extension as Cabal
+import Test.Syd
+import Test.Syd.Aeson
+
+spec :: Spec
+spec = do
+  it "outputs the same json for the cabal2json cabal file" $
+    goldenJSONValueFile
+      "test_resources/json/cabal2json.cabal.json"
+      (Cabal.readGenericPackageDescription Cabal.silent "cabal2json.cabal")
+
+  schemaSpec @Cabal.GenericPackageDescription "generic-package-description"
+  schemaSpec @Cabal.PackageDescription "package-description"
+  schemaSpec @Cabal.PackageIdentifier "package-identified"
+  schemaSpec @Cabal.BenchmarkType "benchmark-type"
+  schemaSpec @Cabal.BenchmarkInterface "benchmark-interface"
+  schemaSpec @Cabal.Benchmark "benchmark"
+  schemaSpec @Cabal.BuildInfo "build-info"
+  schemaSpec @Cabal.BuildType "build-type"
+  schemaSpec @Cabal.Executable "executable"
+  schemaSpec @Cabal.ExecutableScope "executable-scope"
+  schemaSpec @Cabal.Extension "extension"
+  schemaSpec @Cabal.Flag "flag"
+  schemaSpec @Cabal.FlagName "flag-name"
+  schemaSpec @Cabal.ForeignLibType "foreign-lib-type"
+  schemaSpec @Cabal.ForeignLibOption "foreign-lib-option"
+  schemaSpec @Cabal.ForeignLib "foreign-lib"
+  schemaSpec @Cabal.KnownExtension "KnownExtension"
+  schemaSpec @Cabal.Language "language"
+  schemaSpec @Cabal.Library "library"
+  schemaSpec @Cabal.LibraryName "library-name"
+  schemaSpec @Cabal.LibraryVisibility "library-visibility"
+  schemaSpec @Cabal.License "license"
+  schemaSpec @Cabal.ModuleName "module-name"
+  schemaSpec @Cabal.ModuleReexport "module-reexport"
+  schemaSpec @Cabal.PackageIdentifier "package-identifier"
+  schemaSpec @Cabal.PkgconfigVersion "pkgconfig-version"
+  schemaSpec @Cabal.PkgconfigVersionRange "pkgconfig-version-range"
+  schemaSpec @Cabal.ShortText "short-text"
+  schemaSpec @Cabal.SourceRepo "source-repo"
+  schemaSpec @Cabal.TestSuiteInterface "test-suite-interface"
+  schemaSpec @Cabal.TestSuite "test-suite"
+  schemaSpec @Cabal.UnqualComponentName "unqual-component-name"
+  schemaSpec @Cabal.Version "version"
+  schemaSpec @Cabal.VersionRange "version-range"
+  schemaSpec @SPDX.License "spdx-license"
+  schemaSpec @SPDX.LicenseExpression "spdx-license-expression"
+  schemaSpec @SPDX.SimpleLicenseExpression "spdx-simple-license-expression"
+  schemaSpec @SPDX.LicenseExceptionId "spdx-license-exception-id"
+  schemaSpec @SPDX.LicenseRef "spdx-license-ref"
+  schemaSpec @SPDX.LicenseId "spdx-license-id"
+
+schemaSpec :: forall a. HasCodec a => String -> Spec
+schemaSpec name =
+  it ("outputs the same yaml file for " <> name) $
+    pureGoldenByteStringFile
+      ("test_resources/schemas/" <> name <> ".txt")
+      (renderColouredSchemaViaCodec @a)
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF sydtest-discover #-}
