{"record":{"id":"87bb0a315ddd6643","repo":"w7corp/easywechat","slug":"msgtype-cannot-be-empty","errorCode":null,"errorMessage":"msgtype cannot be empty.","messagePattern":"msgtype cannot be empty\\.","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"src/Kernel/Traits/RespondJsonMessage.php","lineNumber":37,"sourceCode":"\n        return $this->createJsonResponse(\n            attributes: $this->normalizeJsonResponse($response),\n            encryptor: $encryptor\n        );\n    }\n\n    /**\n     * @throws InvalidArgumentException\n     */\n    protected function normalizeJsonResponse(mixed $response): array\n    {\n        if (! is_string($response) && is_callable($response)) {\n            $response = $response();\n        }\n\n        if (is_array($response)) {\n            if (! isset($response['msgtype'])) {\n                throw new InvalidArgumentException('msgtype cannot be empty.');\n            }\n\n            return $response;\n        }\n\n        throw new InvalidArgumentException(\n            sprintf('Invalid Response type \"%s\".', gettype($response))\n        );\n    }\n\n    /**\n     * @throws RuntimeException\n     */\n    protected function createJsonResponse(array $attributes, ?Encryptor $encryptor = null): ResponseInterface\n    {\n        $jsonStr = json_encode($attributes, JSON_UNESCAPED_UNICODE);\n\n        if (is_string($jsonStr)) {","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Kernel/Traits/RespondJsonMessage.php#L19-L55","documentation":"Work Server can answer pushes with a JSON passive reply via transformJsonToReply (used when messageType is 'json', src/Work/Server.php:58-60). normalizeJsonResponse() accepts only arrays and requires the lowercase 'msgtype' key — the case-sensitive isset($response['msgtype']) — mirroring WeChat Work's customer-service JSON reply shape. Returning an array without it throws InvalidArgumentException before any JSON is built.","triggerScenarios":"A Work JSON-mode handler returns ['text' => ['content' => 'hi']] without msgtype; returns ['MsgType' => 'text'] (uppercase only works in XML mode); returns a payload shaped for the async customer-service REST API instead of a passive reply.","commonSituations":"Porting code between the customer-service REST API (msgtype+text) and passive JSON replies; one codebase serving both Work JSON mode and OfficialAccount XML mode with different key casing ('msgtype' vs 'MsgType').","solutions":["Return ['msgtype' => 'text', 'text' => ['content' => '...']] from JSON-mode handlers.","Keep casing right: JSON mode wants lowercase msgtype; XML mode wants MsgType.","To send no reply, return '' or null (the trait answers 'success' itself) or a PSR ResponseInterface."],"exampleFix":"// before: Work JSON mode, array without msgtype\nreturn ['Content' => 'hello'];\n// after: required lowercase msgtype plus the message body\nreturn ['msgtype' => 'text', 'text' => ['content' => 'hello']];","handlingStrategy":"validation","validationCode":"if (!isset($reply['msgtype'])) { $reply = ['msgtype' => 'text', 'text' => ['content' => (string) ($reply['Content'] ?? '')]]; }","typeGuard":"function isWorkJsonReply(mixed $r): bool { return is_array($r) && isset($r['msgtype']); }","tryCatchPattern":"try { return $server->serve(); } catch (\\EasyWeChat\\Kernel\\Exceptions\\InvalidArgumentException $e) { if (str_contains($e->getMessage(), 'msgtype')) { \\Log::error('Work JSON reply missing msgtype'); return new \\Nyholm\\Psr7\\Response(200, [], 'success'); } throw $e; }","preventionTips":["Centralize reply builders per mode with the exact key casing","Assert msgtype/MsgType in reply unit tests","Return a PSR ResponseInterface for edge cases"],"tags":["php","wechat-work","json-reply","message-push"],"backgroundTag":"invalid-response-format","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}