diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp
index 06b1989..382a315 100644
--- a/src/mongo/db/curop.cpp
+++ b/src/mongo/db/curop.cpp
@@ -474,6 +474,36 @@ void appendAsObjOrString(StringData name,
 }
 }  // namespace
 
+BSONObj CurOp::serializeGenericCursor(GenericCursor* cursor, boost::optional<size_t> maxQuerySize) {
+    // This creates a new builder to truncate the object that will go into the curOp output. In
+    // order to make sure the object is not too large but not truncate the comment, we only
+    // truncate the originatingCommand and not the entire cursor.
+    if (maxQuerySize) {
+        BSONObjBuilder tempObj;
+        appendAsObjOrString(
+            "truncatedObj", cursor->getOriginatingCommand().get(), maxQuerySize, &tempObj);
+        auto originatingCommand = tempObj.done().getObjectField("truncatedObj");
+        cursor->setOriginatingCommand(originatingCommand.getOwned());
+    }
+    // lsid, ns, and planSummary exist in the top level curop object, so they need to be temporarily
+    // removed from the cursor object to avoid duplicating information.
+    auto lsid = cursor->getLsid();
+    auto ns = cursor->getNs();
+    auto originalPlanSummary(cursor->getPlanSummary() ? boost::none
+                                                      : boost::optional<std::string>(
+                                                            cursor->getPlanSummary()->toString()));
+    cursor->setLsid(boost::none);
+    cursor->setNs(boost::none);
+    cursor->setPlanSummary(boost::none);
+    auto serialized = cursor->toBSON();
+    cursor->setLsid(lsid);
+    cursor->setNs(ns);
+    if (originalPlanSummary) {
+        cursor->setPlanSummary(StringData(*originalPlanSummary));
+    }
+    return serialized;
+}
+
 void CurOp::reportState(BSONObjBuilder* builder, bool truncateOps) {
     if (_start) {
         builder->append("secs_running", durationCount<Seconds>(elapsedTimeTotal()));
@@ -496,23 +526,7 @@ void CurOp::reportState(BSONObjBuilder* builder, bool truncateOps) {
     }
 
     if (_genericCursor) {
-        // This creates a new builder to truncate the object that will go into the curOp output. In
-        // order to make sure the object is not too large but not truncate the comment, we only
-        // truncate the originatingCommand and not the entire cursor.
-        BSONObjBuilder tempObj;
-        appendAsObjOrString(
-            "truncatedObj", _genericCursor->getOriginatingCommand().get(), maxQuerySize, &tempObj);
-        auto originatingCommand = tempObj.done().getObjectField("truncatedObj");
-        _genericCursor->setOriginatingCommand(originatingCommand.getOwned());
-        // lsid and ns exist in the top level curop object, so they need to be temporarily
-        // removed from the cursor object to avoid duplicating information.
-        auto lsid = _genericCursor->getLsid();
-        auto ns = _genericCursor->getNs();
-        _genericCursor->setLsid(boost::none);
-        _genericCursor->setNs(boost::none);
-        builder->append("cursor", _genericCursor->toBSON());
-        _genericCursor->setLsid(lsid);
-        _genericCursor->setNs(ns);
+        builder->append("cursor", serializeGenericCursor(&(*_genericCursor), maxQuerySize));
     }
 
     if (!_message.empty()) {
diff --git a/src/mongo/db/curop.h b/src/mongo/db/curop.h
index 3f3fe4b..1731679 100644
--- a/src/mongo/db/curop.h
+++ b/src/mongo/db/curop.h
@@ -242,6 +242,14 @@ public:
                                          BSONObjBuilder* infoBuilder);
 
     /**
+     * Serializes the fields of a GenericCursor which do not appear elsewhere in the currentOp
+     * output. If 'maxQuerySize' is given, truncates the cursor's originatingCommand but preserves
+     * the comment.
+     */
+    static BSONObj serializeGenericCursor(GenericCursor* cursor,
+                                          boost::optional<size_t> maxQuerySize);
+
+    /**
      * Constructs a nested CurOp at the top of the given "opCtx"'s CurOp stack.
      */
     explicit CurOp(OperationContext* opCtx);
diff --git a/src/mongo/db/pipeline/mongo_process_common.cpp b/src/mongo/db/pipeline/mongo_process_common.cpp
index 9609a49..bc3324c 100644
--- a/src/mongo/db/pipeline/mongo_process_common.cpp
+++ b/src/mongo/db/pipeline/mongo_process_common.cpp
@@ -36,6 +36,7 @@
 #include "mongo/db/auth/authorization_manager.h"
 #include "mongo/db/auth/authorization_session.h"
 #include "mongo/db/client.h"
+#include "mongo/db/curop.h"
 #include "mongo/db/operation_context.h"
 #include "mongo/db/pipeline/expression_context.h"
 #include "mongo/db/service_context.h"
@@ -82,33 +83,24 @@ std::vector<BSONObj> MongoProcessCommon::getCurrentOps(
 
         for (auto&& cursor : getIdleCursors(expCtx, userMode)) {
             BSONObjBuilder cursorObj;
-            auto ns = cursor.getNs();
-            auto lsid = cursor.getLsid();
             cursorObj.append("type", "idleCursor");
             cursorObj.append("host", getHostNameCached());
+
+            // First, extract fields which need to go at the top level out of the GenericCursor.
+            auto ns = cursor.getNs();
             cursorObj.append("ns", ns->toString());
-            // If in legacy read mode, lsid is not present.
-            if (lsid) {
+            if (auto lsid = cursor.getLsid()) {
                 cursorObj.append("lsid", lsid->toBSON());
             }
-            cursor.setNs(boost::none);
-            cursor.setLsid(boost::none);
-            // On mongos, planSummary is not present.
-            auto planSummaryData = cursor.getPlanSummary();
-            if (planSummaryData) {
-                auto planSummaryText = planSummaryData->toString();
-                // Plan summary has to appear in the top level object, not the cursor object.
-                // We remove it, create the op, then put it back.
-                cursor.setPlanSummary(boost::none);
-                cursorObj.append("planSummary", planSummaryText);
-                cursorObj.append("cursor", cursor.toBSON());
-                cursor.setPlanSummary(StringData(planSummaryText));
-            } else {
-                cursorObj.append("cursor", cursor.toBSON());
+            if (auto planSummaryData = cursor.getPlanSummary()) {  // Not present on mongos.
+                cursorObj.append("planSummary", *planSummaryData);
             }
+
+            // Next, append the stripped-down version of the generic cursor. This will avoid
+            // duplicating information reported at the top level.
+            cursorObj.append("cursor", CurOp::serializeGenericCursor(&cursor, boost::none));
+
             ops.emplace_back(cursorObj.obj());
-            cursor.setNs(ns);
-            cursor.setLsid(lsid);
         }
     }
 
