Web Connection
Invalid JSON body passed when calling a REST service
Gravatar is a globally recognized avatar based on your email address. Invalid JSON body passed when calling a REST service
  Richard Kaye
  All
  Oct 31, 2024 @ 10:48am

Hi Rick,

I'm working on integrating a call from my app to a new internal REST service and I'm getting the error response shown in the post title. I've no doubt that I'm doing something wonky but figuring that out has turned into a challenge.

Running in my dev I can stop at the point where I've got everything prepared, take the headers and payload, copy them into a Websurge request hitting the same endpoint, and I get a 200.

But when I call it in the context of a wwJsonServiceClient object's CallService method I get a 400. The only thing I can think of at the moment is the service doesn't like the escaped double quotes in the post buffer but that doesn't make much sense.

Here's a snippet from the method that calls the API:

loSendBatch=CREATEOBJECT("wwJsonServiceClient")
WITH m.loSendBatch
	.lSaveRequestData=.t.
	.CreateWWHTTP()
	.CreateSerializer()
	.oSerializer.PropertyNameOverrides=[batchId,userName,dateSubmitted,invBalance,approvalId]
	.oHttp.cContentType=[application/json; charset=utf-8]
	* add headers 
	.oHttp.AddHeader("Authorization", m.lcBearerToken)
	.oHttp.AddHeader("X-RFC-Signature", m.lcHash)
	.oHttp.nConnectTimeout=600
	* - serialize json from parameter object.
	lcJson=STRCONV(.oSerializer.Serialize(m.toJson),9)
	.oHttp.AddPostKey(m.lcJSON)
	m.llRetval=.t.
	* tbd - response handling
	lcEndpoint=[<snip>]
	luResponse=.CallService(m.lcEndpoint, .oHttp)  
	IF .oHttp.cResultCode=[200]	&& success
		m.llRetval=.t.
	ELSE 
		ASSERT VARTYPE(m.luResponse)=[O] MESSAGE [why is the response not an object?]
		IF VARTYPE(m.luResponse)=[O]	&& response is a json object
			m.llRetval=.f. 
			m.lcString=CRLF+[batch ]+TRANSFORM(m.tnBatchID)+[ rejected]+CRLF
			m.lcString=m.lcString+ALLTRIM(m.luResponse.code)+CRLF
			m.lcString=m.lcString+ALLTRIM(m.luResponse.Message)+CRLF
			m.lcString=m.lcString+TRANSFORM(m.luResponse.data.json_error_code)+CRLF
			m.lcString=m.lcString+ALLTRIM(m.luResponse.data.json_error_message)+CRLF
			m.lcString=m.lcString+TRANSFORM(m.luResponse.data.Status)+CRLF
			m.lcString=m.lcString+[Full request (encoded)]+CRLF
			m.lcString=m.lcString+ALLTRIM(.cRequestData)+CRLF
			m.lcString=m.lcString+[Full response]+CRLF
			m.lcString=m.lcString+ALLTRIM(.cResponseData)+CRLF
			logEvent(m.lcString,FORCEEXT([.\Logs\sendBatchPayments_]+DTOS(DATE()),[log]))	&& tbd
		ELSE 
	* tbd?
		ENDIF 
	ENDIF 
ENDWITH && m.loSendBatch
RETURN m.llRetval

I know there are some explicit calls that I should not need to make such as creating the oHTTP and oSerializer objects but as part of my troubleshooting I'm trying to remove assumptions. As you can see I've set the object lSaveRequestData property to .t. and am writing that out to a log.

Here's the log slightly sanitized for my protection and reformatted to make it easier to read:


10/31/2024 03:55 PM: 
batch 50 rejected
rest_invalid_json
Invalid JSON body passed.
4
Syntax error
400

Full request (encoded)

{"ccontenttype": "application/json; charset=utf-8",
"cerrormsg": "",
"cextraheaders": "Authorization: Bearer <snip>\r\nX-RFC-Signature: tsSCCrKewFGXQZd6lJDe9ilkr6ZqNyjE8Zo+ugrrs+o=\r\n",
"chttpheaders": "",
"chttpproxybypass": "",
"chttpproxyname": "",
"chttpproxypassword": "",
"chttpproxyusername": "",
"chttpverb": "POST",
"clink": "",
"cpassword": "",
"cpostbuffer": "{\"batchId\": 50,\"charges\": [{\"approvalId\": 330713121,\"invBalance\": 125,\"invno\": \"10147-WL54\"}],\"dateSubmitted\": \"2024-11-01T01:53:44Z\",\"userName\": \"test@test.com\"}",
"cresultcode": "",
"cresultcodemessage": "",
"cserver": "",
"cuseragent": "West Wind Internet Protocols 8",
"cusername": "",
"cversion": "8",
"lallowgzip": false,
"lcacherequest": false,
"ldecodeutf8": false,
"lhttpcanceldownload": false,
"lignorecertificatewarnings": false,
"lsecurelink": false,
"luselargepostbuffer": false,
"name": "Wwhttp",
"nclientcertnumberindex": 0,
"nconnecttimeout": 600,
"ncontentsize": 0,
"nerror": 0,
"nhttpconnecttype": 0,
"nhttpport": 0,
"nhttppostmode": 1,
"nhttpserviceflags": 0,
"nhttpworkbuffersize": 65556,
"nserviceflags": 0,
"opoststream": {"cfilename": "C:\\USERS\\RKAYE\\APPDATA\\LOCAL\\TEMP\\{D21A9C5D-ACF7-4D82-9BCB-E9C3AB71D581}.txt",
"name": "Wwfilestream",
"nhandle": 125,
"nlength": 0}}

Full response

{"code":"rest_invalid_json",
"message":"Invalid JSON body passed.",
"data":{"status":400,
        "json_error_code":4,
        "json_error_message":"Syntax error"
        }
}

Any thoughts you care to share will be much appreciated.

TIA

Gravatar is a globally recognized avatar based on your email address. re: Invalid JSON body passed when calling a REST service
  Rick Strahl
  Richard Kaye
  Oct 31, 2024 @ 05:07pm

Wow - you really know how to make something simple very complicated! Your code is posting the .oHttp object instance data to the server 😄

If you'd have to do all that why would you even use this component? It would be easier to just use wwhttp and wwJsonSerializer manually.

You don't need to do anything with the HTTP object unless you need to add custom headers. Otherwise just pass your object (not the JSON) to .CallService() and it'll handle the rest.

loSendBatch=CREATEOBJECT("wwJsonServiceClient")
WITH m.loSendBatch
	.lSaveRequestData=.t.
	.oSerializer.PropertyNameOverrides=[batchId,userName,dateSubmitted,invBalance,approvalId]	
	* add custom headers 
	.oHttp.AddHeader("Authorization", m.lcBearerToken)
	.oHttp.AddHeader("X-RFC-Signature", m.lcHash)
	.oHttp.nConnectTimeout=600
	lcEndpoint=[<snip>]
	luResponse=.CallService(m.lcEndpoint, m.toJson)  

I urge you to re-read the topic linked above a couple of times and use the approach recommended at the bottom of topic description - use a service class that holds all your service calls and manage each call in small blocks with method wrappers that return clean results, handle errors cleanly and return a logical error response in case something goes wrong (as it often can with service calls).

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Invalid JSON body passed when calling a REST service
  Richard Kaye
  Rick Strahl
  Nov 1, 2024 @ 01:44am

Reading and truly comprehending are different... 😃

I knew it was over-complicated. Thanks for the pointers. I appreciate your time.

© 1996-2024