Odd JSON request - Forum - OpenEdge Development - Progress Community
 Forum

Odd JSON request

  • 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? 

  • 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"? :)

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

  • 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.

  • 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.
     
     
  • 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.

  • Please log a bug with TS for this.

    Done - Case # 341493

  • 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.
     
  • 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)