diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs
index 3bac5a8e5d749..4c992e94833d6 100644
--- a/src/librustdoc/passes/strip_priv_imports.rs
+++ b/src/librustdoc/passes/strip_priv_imports.rs
@@ -12,5 +12,6 @@ pub(crate) const STRIP_PRIV_IMPORTS: Pass = Pass {
 };
 
 pub(crate) fn strip_priv_imports(krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate {
-    ImportStripper { tcx: cx.tcx }.fold_crate(krate)
+    let is_json_output = cx.output_format.is_json() && !cx.show_coverage;
+    ImportStripper { tcx: cx.tcx, is_json_output }.fold_crate(krate)
 }
diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs
index 8fc42462de969..bb6dccb7c9499 100644
--- a/src/librustdoc/passes/strip_private.rs
+++ b/src/librustdoc/passes/strip_private.rs
@@ -28,7 +28,8 @@ pub(crate) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) ->
             is_json_output,
             tcx: cx.tcx,
         };
-        krate = ImportStripper { tcx: cx.tcx }.fold_crate(stripper.fold_crate(krate));
+        krate =
+            ImportStripper { tcx: cx.tcx, is_json_output }.fold_crate(stripper.fold_crate(krate));
     }
 
     // strip all impls referencing private items
diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs
index f8a0d77538d39..f5501b3d5238b 100644
--- a/src/librustdoc/passes/stripper.rs
+++ b/src/librustdoc/passes/stripper.rs
@@ -243,12 +243,25 @@ impl<'a> DocFolder for ImplStripper<'a, '_> {
 /// This stripper discards all private import statements (`use`, `extern crate`)
 pub(crate) struct ImportStripper<'tcx> {
     pub(crate) tcx: TyCtxt<'tcx>,
+    pub(crate) is_json_output: bool,
+}
+
+impl<'tcx> ImportStripper<'tcx> {
+    fn import_should_be_hidden(&self, i: &Item, imp: &clean::Import) -> bool {
+        if self.is_json_output {
+            // FIXME: This should be handled the same way as for HTML output.
+            imp.imported_item_is_doc_hidden(self.tcx)
+        } else {
+            i.attrs.lists(sym::doc).has_word(sym::hidden)
+        }
+    }
 }
 
 impl<'tcx> DocFolder for ImportStripper<'tcx> {
     fn fold_item(&mut self, i: Item) -> Option<Item> {
         match *i.kind {
-            clean::ImportItem(imp) if imp.imported_item_is_doc_hidden(self.tcx) => None,
+            clean::ImportItem(imp) if self.import_should_be_hidden(&i, &imp) => None,
+            clean::ImportItem(_) if i.attrs.lists(sym::doc).has_word(sym::hidden) => None,
             clean::ExternCrateItem { .. } | clean::ImportItem(..)
                 if i.visibility(self.tcx) != Some(Visibility::Public) =>
             {
diff --git a/tests/rustdoc/reexport-doc-hidden.rs b/tests/rustdoc/reexport-doc-hidden.rs
new file mode 100644
index 0000000000000..3ea5fde72f711
--- /dev/null
+++ b/tests/rustdoc/reexport-doc-hidden.rs
@@ -0,0 +1,26 @@
+// Part of <https://github.com/rust-lang/rust/issues/59368>.
+// This test ensures that reexporting a `doc(hidden)` item will
+// still show the reexport.
+
+#![crate_name = "foo"]
+
+#[doc(hidden)]
+pub type Type = u32;
+
+// @has 'foo/index.html'
+// @has - '//*[@id="reexport.Type2"]/code' 'pub use crate::Type as Type2;'
+pub use crate::Type as Type2;
+
+// @count - '//*[@id="reexport.Type3"]' 0
+#[doc(hidden)]
+pub use crate::Type as Type3;
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! foo {
+    () => {};
+}
+
+// This is a bug: https://github.com/rust-lang/rust/issues/59368
+// @!has - '//*[@id="reexport.Macro"]/code' 'pub use crate::foo as Macro;'
+pub use crate::foo as Macro;