{"record":{"id":"f931afb20089d855","repo":"jackwener/OpenCLI","slug":"nowcoder-returned-a-malformed-feed-row","errorCode":null,"errorMessage":"Nowcoder returned a malformed feed row","messagePattern":"Nowcoder returned a malformed feed row","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/nowcoder/posts.js","lineNumber":126,"sourceCode":"}\n\nfunction commonFeedFields(data, post, postType, authorId, timestamp, index) {\n    if (!isRecord(data.frequencyData)) throw new CommandExecutionError(`Nowcoder returned malformed ${postType} frequencyData`);\n    return {\n        rank: index + 1,\n        post_type: postType,\n        title: optionalText(post.title, `${postType} title`) || '(untitled)',\n        ...authorFields(data.userBrief, authorId, postType),\n        content: cleanBody(post.content, `${postType} content`),\n        likes: metric(data.frequencyData, 'likeCnt'),\n        comments: metric(data.frequencyData, 'commentCnt'),\n        views: metric(data.frequencyData, 'viewCnt'),\n        time: isoTime(timestamp, `${postType} timestamp`),\n    };\n}\n\nfunction projectFeedData(data, index, requireOuterId) {\n    if (!isRecord(data)) throw new CommandExecutionError('Nowcoder returned a malformed feed row');\n    if (data.contentType === CONTENT_FEED_TYPE) {\n        if (!isRecord(data.contentData) || data.momentData != null || data.contentData.entityType !== CONTENT_ENTITY_TYPE) {\n            throw new CommandExecutionError('Nowcoder returned mismatched content feed data');\n        }\n        const post = data.contentData;\n        const id = requiredId(post.id, 'content id');\n        if ((requireOuterId || data.contentId != null) && id !== requiredId(data.contentId, 'content feed id')) {\n            throw new CommandExecutionError('Nowcoder returned mismatched content feed identity');\n        }\n        const uuid = requiredUuid(post.uuid, 'content uuid');\n        return {\n            ...commonFeedFields(data, post, 'content', post.authorId, post.createTime, index),\n            id,\n            uuid,\n            entity_id: requiredId(post.entityId, 'content entity id'),\n            url: `https://www.nowcoder.com/discuss/${id}`,\n        };\n    }","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/nowcoder/posts.js#L108-L144","documentation":"projectFeedData validates each raw feed row returned by Nowcoder's API before projecting it into a normalized post. This error is thrown when a row is not a plain non-null, non-array object (fails the isRecord check), meaning the upstream feed payload contained null, an array, a primitive, or a missing entry where a feed row was expected. The library throws CommandExecutionError to fail fast rather than silently emitting corrupted post data.","triggerScenarios":"projectNowcoderFeed passes each element of the records array (or record.data when wrapped=true) into projectFeedData; this fires for any element that is null, undefined, an array, or a primitive (string/number/boolean) instead of a feed row object.","commonSituations":"Nowcoder changes its feed API response shape and omits 'data' on wrapped records; an API page returns null rows for deleted/removed posts; passing your own hand-built array to projectNowcoderFeed with a wrong element type; forgetting wrapped=true when the source returns {data: ...} envelopes so record?.data is undefined.","solutions":["Check whether wrapped=true matches the actual API response; if rows are {data:...} envelopes, pass wrapped=true so the inner object is projected.","Log and filter the raw records array before calling projectNowcoderFeed to drop null/primitive rows from deleted posts.","Call fetchNowcoderData to obtain records so the response envelope is validated before projection.","If Nowcoder changed its schema, update the projection code to the new field layout."],"exampleFix":"// before\nprojectNowcoderFeed(response.records, 10, 'recommend'); // rows are {data:...}\n// after\nprojectNowcoderFeed(response.records, 10, 'recommend', true);","handlingStrategy":"type-guard","validationCode":"// before calling projectNowcoderFeed\nconst isValidRow = (r) => r != null && typeof r === 'object' && !Array.isArray(r);\nif (!records.every((r) => isValidRow(wrapped ? r?.data : r))) throw new Error('feed contains malformed rows');","typeGuard":"function isRecord(value) { return Boolean(value) && typeof value === 'object' && !Array.isArray(value); }","tryCatchPattern":"try {\n    rows = projectNowcoderFeed(records, limit, source, wrapped);\n} catch (error) {\n    if (String(error.message).includes('malformed feed row')) {\n        rows = projectNowcoderFeed(records.filter((r) => isRecord(wrapped ? r?.data : r)), limit, source, wrapped);\n    } else throw error;\n}","preventionTips":["Always fetch records via fetchNowcoderData so the response envelope is validated first","Match wrapped=true to APIs that return {data:...} row envelopes","Pre-filter null/primitive rows (deleted posts) before projection","Log the raw response when the schema is unfamiliar"],"tags":["feed-parsing","api-response","validation"],"backgroundTag":"api-response-schema-changed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}