Odd JSON request

Posted by Tim Kuehn on 10-Mar-2016 18:04

I have a REST service I'm calling which is expecting a payload like this:

PUT localhost:7474/.../foo
Accept: application/json; charset=UTF-8
Content-Type: application/json

"bar"

Supposedly "bar" by itself is valid JSON, but I'm not finding a way to create it. Is there a way to do this? 

All Replies

Posted by Marian Edu on 11-Mar-2016 00:51

Why supposedly? Most probably all JSON parsers will interpret that as a value (same with numbers and logical expressions)... problem is, what exactly do you need to 'generate' a text like "bar"? :)

Posted by AdrianJones on 11-Mar-2016 03:55

check out the google chrome extension "postman" if you need to test/call a rest api

Posted by Tim Kuehn on 11-Mar-2016 07:36

Adrian - the issue right now is generating the payload. The current JSON implementation requires a variable name and value, and the result is {"foo": "bar"}.

I can't do that for this use case, I need just the "bar" part in the message body.

Posted by Peter Judge on 11-Mar-2016 08:48

This is because the http client uses the built-in JsonObject class to build the payload and those classes only support objects and arrays, not the primitive types (strings, numbers, booleans  and null) per www.ietf.org/.../rfc4627.txt .
 
Please log a bug with TS for this.
 
I can offer a workaround that requires some changes to your code (based on the 11.6 code) in the interim.
 
First, create a body writer that does what you need. The http client uses pluggable modules for writing various content types.  An example called MyJsonBodyWriter appears below. This version can support null and the primitive values in addition to objects and arrays. Support for logical and numeric values is via the ByteBucket/Memptr types and there's no validation on whether those are valid JSON, so YMMV. To pass a null value, create a ByteBucket with a string of NULL  ( myBB:PutString('NULL') should do the trick ).
 
Second, register your new writer as the handler for application/json . You need to do this once per session, before you make your requests.
 
using OpenEdge.Net.HTTP.Filter.Writer.BodyWriterRegistry.
BodyWriterRegistry:Registry:Put('application/json':u, get-class(MyJsonBodyWriter)).
 
Third, change how you build the request, in order to override the default Content-Type inference
 
def var s as OpenEdge.Core.String.
def var myReq as IHttpRequest.
 
s = new String('bar').
myReq = RequestBuilder:Post(URI:Parse('http://httpbin.org/post'),
                            // override the default content-type inferred from the object's type
                            s, 'application/json')
                      :Request.
 
You can use String, JsonObject, JsonArray, Memptr and ByeBucket instances for the payloads in this example.
 
Things should now work as expected.
 
JSON Writer class.
 
block-level on error undo, throw.
 
using OpenEdge.Core.ByteBucket.
using OpenEdge.Core.String.
using OpenEdge.Net.HTTP.Filter.Payload.MessageWriter.
using Progress.Json.ObjectModel.JsonConstruct.
using Progress.Lang.AppError.
using Progress.Lang.Object.
using OpenEdge.Core.Memptr.
 
class MyJsonBodyWriter inherits MessageWriter:
    
    constructor public MyJsonBodyWriter():
        super (get-class(ByteBucket)).
    end constructor.   
    
    method override public void Open():   
        if not valid-object(this-object:Entity) then
            assign this-object:Entity = ByteBucket:Instance().
       
        super:Open().
    end method.
   
    method override public int64 Write( input poData as Object):
        define variable mJson as memptr no-undo.
        define variable iBytesWritten as int64 no-undo.
       
        case true:
            when not valid-object(poData) then
            do:
                cast(this-object:Entity, ByteBucket):Clear().
               
                cast(this-object:Entity, ByteBucket):PutString("null":u).
                assign iBytesWritten = 4.
            end.    // nulls/unknowns
            when type-of(poData, Memptr) then
            do:
                cast(this-object:Entity, ByteBucket):Clear().
                cast(this-object:Entity, ByteBucket):PutBytes(cast(poData, Memptr)).
               
                assign iBytesWritten = cast(poData, Memptr):Size.
            end.    // bytes: Memptr
            when type-of(poData, ByteBucket) then
            do:
                cast(this-object:Entity, ByteBucket):Clear().
                cast(this-object:Entity, ByteBucket):PutBytes(cast(poData, ByteBucket)).
               
                assign iBytesWritten = cast(poData, ByteBucket):Size.
            end.    // bytes: Bucket                       
            when type-of(poData, String) then
            do:
                cast(this-object:Entity, ByteBucket):Clear().
               
                cast(this-object:Entity, ByteBucket):PutString(substitute("~"&1~"":u,
                                                               cast(poData, String):Value)).
                assign iBytesWritten = cast(poData, String):size + 2.
            end.    // String
            when type-of(poData, JsonConstruct) then
            do on error undo, throw:
                cast(this-object:Entity, ByteBucket):Clear().
               
                cast(poData, JsonConstruct):Write(mJson).
               
                /* Add the JSON to the message body's bytebucket */
                assign iBytesWritten = get-size(mJson).
               
                cast(this-object:Entity, ByteBucket):PutBytes(get-pointer-value(mJson),
                                                 iBytesWritten).
                finally:
                    set-size(mJson) = 0.
                end finally.       
            end.    // JSON
            otherwise
                return error new AppError(
                                substitute('Unsupported object type: &1', poData:GetClass():TypeName)
                                , 0).
        end case.
               
        return iBytesWritten.
    end method.
   
end class.
 
 

Posted by egarcia on 11-Mar-2016 08:55

Hello,

I guess that you could use a CHARACTER or LONGCHAR parameter to return the string.

(Similar to how the following sample returns the JSON text: community.progress.com/.../image3.png )

However, if you have special characters in the string you would need to the serialization on your own. (Or use a JsonObject variable to get the serialized string and extract it from it.)

BTW, if you need to validate the JSON value that you are generating, you could use http://jsonlint.com/

It seems that the JSON API in the ABL only supports RFC 4627 but not the updated RFC 7158

In RFC 4627, a JSON document was defined as only an object or an array. RFC 7158 removes this restriction.

( tools.ietf.org/.../rfc7158 ).

I hope this helps.

Posted by Tim Kuehn on 11-Mar-2016 09:07

Please log a bug with TS for this.

Done - Case # 341493

Posted by Peter Judge on 11-Mar-2016 09:12

Thanks.
 
Just FYI, if you get 'bar' as a response that's got a Content-Type of application/json you are going to receive a ByteBucket-typed Entity as opposed to a JsonObject-typed entity.
 

Posted by Jean-Christophe Cardot on 29-Sep-2017 08:36

As of today (11.6.3) it still does not work. I have a rest ws consuming application/json, when I POST true as the body, PASOE crashes. If I post a string like yours (e.g. "toto"), I get an error. Will it be ok in 11.6.4?

(both values are valid JSON)

This thread is closed