diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index cf18724ef..e05644ab2 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -5,6 +5,7 @@
 #include "worker-protocol.hh"
 #include "fs-accessor.hh"
 #include <boost/container/small_vector.hpp>
+#include <nlohmann/json.hpp>
 
 namespace nix {
 
@@ -890,4 +891,62 @@ std::optional<BasicDerivation> Derivation::tryResolve(
 
 const Hash impureOutputHash = hashString(htSHA256, "impure");
 
+nlohmann::json DerivationOutput::toJSON(
+    const Store & store, std::string_view drvName, std::string_view outputName) const
+{
+    nlohmann::json res = nlohmann::json::object();
+    std::visit(overloaded {
+        [&](const DerivationOutput::InputAddressed & doi) {
+            res["path"] = store.printStorePath(doi.path);
+        },
+        [&](const DerivationOutput::CAFixed & dof) {
+            res["path"] = store.printStorePath(dof.path(store, drvName, outputName));
+            res["hashAlgo"] = dof.hash.printMethodAlgo();
+            res["hash"] = dof.hash.hash.to_string(Base16, false);
+        },
+        [&](const DerivationOutput::CAFloating & dof) {
+            res["hashAlgo"] = makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType);
+        },
+        [&](const DerivationOutput::Deferred &) {},
+        [&](const DerivationOutput::Impure & doi) {
+            res["hashAlgo"] = makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType);
+            res["impure"] = true;
+        },
+    }, raw());
+    return res;
+}
+
+nlohmann::json Derivation::toJSON(const Store & store) const
+{
+    nlohmann::json res = nlohmann::json::object();
+
+    {
+        nlohmann::json & outputsObj = res["outputs"];
+        outputsObj = nlohmann::json::object();
+        for (auto & [outputName, output] : outputs) {
+            outputsObj[outputName] = output.toJSON(store, name, outputName);
+        }
+    }
+
+    {
+        auto& inputsList = res["inputSrcs"];
+        inputsList = nlohmann::json ::array();
+        for (auto & input : inputSrcs)
+            inputsList.emplace_back(store.printStorePath(input));
+    }
+
+    {
+        auto& inputDrvsObj = res["inputDrvs"];
+        inputDrvsObj = nlohmann::json ::object();
+        for (auto & input : inputDrvs)
+            inputDrvsObj[store.printStorePath(input.first)] = input.second;
+    }
+
+    res["system"] = platform;
+    res["builder"] = builder;
+    res["args"] = args;
+
+    return res;
+}
+
 }
diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh
index f42c13cdc..8456b29e7 100644
--- a/src/libstore/derivations.hh
+++ b/src/libstore/derivations.hh
@@ -83,6 +83,11 @@ struct DerivationOutput : _DerivationOutputRaw
     inline const Raw & raw() const {
         return static_cast<const Raw &>(*this);
     }
+
+    nlohmann::json toJSON(
+        const Store & store,
+        std::string_view drvName,
+        std::string_view outputName) const;
 };
 
 typedef std::map<std::string, DerivationOutput> DerivationOutputs;
@@ -210,6 +215,8 @@ struct Derivation : BasicDerivation
     Derivation() = default;
     Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { }
     Derivation(BasicDerivation && bd) : BasicDerivation(std::move(bd)) { }
+
+    nlohmann::json toJSON(const Store & store) const;
 };
 
 
diff --git a/src/libstore/tests/derivation.cc b/src/libstore/tests/derivation.cc
new file mode 100644
index 000000000..c9d404188
--- /dev/null
+++ b/src/libstore/tests/derivation.cc
@@ -0,0 +1,115 @@
+#include <nlohmann/json.hpp>
+#include <gtest/gtest.h>
+
+#include "derivations.hh"
+
+#include "tests/libstore.hh"
+
+namespace nix {
+
+class DerivationTest : public LibStoreTest
+{
+};
+
+#define TEST_JSON(TYPE, NAME, STR, VAL, ...)                           \
+    TEST_F(DerivationTest, TYPE ## _ ## NAME ## _to_json) {            \
+        using nlohmann::literals::operator "" _json;                   \
+        ASSERT_EQ(                                                     \
+            STR ## _json,                                              \
+            (TYPE { VAL }).toJSON(*store __VA_OPT__(,) __VA_ARGS__));  \
+    }
+
+TEST_JSON(DerivationOutput, inputAddressed,
+    R"({
+        "path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
+    })",
+    (DerivationOutput::InputAddressed {
+        .path = store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"),
+    }),
+    "drv-name", "output-name")
+
+TEST_JSON(DerivationOutput, caFixed,
+    R"({
+        "hashAlgo": "r:sha256",
+        "hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f",
+        "path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
+    })",
+    (DerivationOutput::CAFixed {
+        .hash = {
+            .method = FileIngestionMethod::Recursive,
+            .hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
+        },
+    }),
+    "drv-name", "output-name")
+
+TEST_JSON(DerivationOutput, caFloating,
+    R"({
+        "hashAlgo": "r:sha256"
+    })",
+    (DerivationOutput::CAFloating {
+        .method = FileIngestionMethod::Recursive,
+        .hashType = htSHA256,
+    }),
+    "drv-name", "output-name")
+
+TEST_JSON(DerivationOutput, deferred,
+    R"({ })",
+    DerivationOutput::Deferred { },
+    "drv-name", "output-name")
+
+TEST_JSON(DerivationOutput, impure,
+    R"({
+        "hashAlgo": "r:sha256",
+        "impure": true
+    })",
+    (DerivationOutput::Impure {
+        .method = FileIngestionMethod::Recursive,
+        .hashType = htSHA256,
+    }),
+    "drv-name", "output-name")
+
+TEST_JSON(Derivation, impure,
+    R"({
+      "inputSrcs": [
+        "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
+      ],
+      "inputDrvs": {
+        "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": [
+          "cat",
+          "dog"
+        ]
+      },
+      "system": "wasm-sel4",
+      "builder": "foo",
+      "args": [
+        "bar",
+        "baz"
+      ],
+      "outputs": {}
+    })",
+    ({
+        Derivation drv;
+        drv.inputSrcs = {
+            store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"),
+        };
+        drv.inputDrvs = {
+            {
+                store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"),
+                {
+                    "cat",
+                    "dog",
+                },
+            }
+        };
+        drv.platform = "wasm-sel4";
+        drv.builder = "foo";
+        drv.args = {
+            "bar",
+            "baz",
+        };
+        drv;
+    }))
+
+#undef TEST_JSON
+
+}
diff --git a/src/nix/show-derivation.cc b/src/nix/show-derivation.cc
index af2e676a4..d1a516cad 100644
--- a/src/nix/show-derivation.cc
+++ b/src/nix/show-derivation.cc
@@ -54,56 +54,8 @@ struct CmdShowDerivation : InstallablesCommand
         for (auto & drvPath : drvPaths) {
             if (!drvPath.isDerivation()) continue;
 
-            json& drvObj = jsonRoot[store->printStorePath(drvPath)];
-
-            auto drv = store->readDerivation(drvPath);
-
-            {
-                json& outputsObj = drvObj["outputs"];
-                outputsObj = json::object();
-                for (auto & [_outputName, output] : drv.outputs) {
-                    auto & outputName = _outputName; // work around clang bug
-                    auto& outputObj = outputsObj[outputName];
-                    outputObj = json::object();
-                    std::visit(overloaded {
-                        [&](const DerivationOutput::InputAddressed & doi) {
-                            outputObj["path"] = store->printStorePath(doi.path);
-                        },
-                        [&](const DerivationOutput::CAFixed & dof) {
-                            outputObj["path"] = store->printStorePath(dof.path(*store, drv.name, outputName));
-                            outputObj["hashAlgo"] = dof.hash.printMethodAlgo();
-                            outputObj["hash"] = dof.hash.hash.to_string(Base16, false);
-                        },
-                        [&](const DerivationOutput::CAFloating & dof) {
-                            outputObj["hashAlgo"] = makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType);
-                        },
-                        [&](const DerivationOutput::Deferred &) {},
-                        [&](const DerivationOutput::Impure & doi) {
-                            outputObj["hashAlgo"] = makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType);
-                            outputObj["impure"] = true;
-                        },
-                    }, output.raw());
-                }
-            }
-
-            {
-                auto& inputsList = drvObj["inputSrcs"];
-                inputsList = json::array();
-                for (auto & input : drv.inputSrcs)
-                    inputsList.emplace_back(store->printStorePath(input));
-            }
-
-            {
-                auto& inputDrvsObj = drvObj["inputDrvs"];
-                inputDrvsObj = json::object();
-                for (auto & input : drv.inputDrvs)
-                    inputDrvsObj[store->printStorePath(input.first)] = input.second;
-            }
-
-            drvObj["system"] = drv.platform;
-            drvObj["builder"] = drv.builder;
-            drvObj["args"] = drv.args;
-            drvObj["env"] = drv.env;
+            jsonRoot[store->printStorePath(drvPath)] =
+                store->readDerivation(drvPath).toJSON(*store);
         }
         std::cout << jsonRoot.dump(2) << std::endl;
     }