Question
· Sep 21, 2020

%JSON.Adaptor export to %DynamicObject aka native JSON

Doing a new project with %JSON.Adaptor, unexpectedly realized that %JSON.Adaptor does not support export to native JSON. %JSONExport just outputs directly to the current device, and there are two more methods %JSONExportToString, and %JSONExportToStream.

In conjunction with generating REST from swagger specification, where any generated method accepts as a result %DynamicObject, which is good. 

I have multiple places in my REST where I have to return JSON for an object, but I have to modify the result a bit, just extend it with some other way.

And having this in my code, I don't like, and anyway I'm not able to add some more properties on the object level.

ClassMethod outputObject(kind As %String, object As %JSON.Adaptor) As %String
{
    $$$ThrowOnError(object.%JSONExportToString(.output))
    Quit "{ """ _ kind _ """: " _ output _ "}"
}

 

And one more question, is there any way on how to export id of the object?

Discussion (7)1
Log in or sign up to continue

Good morning Dmitriy - I'm not sure I 100% understand what you're asking but in my experience with %JSON.Adaptor,  there is only one additional step you need to do to get the string into %DynamicObject:

Set tDynObj = {}.%FromJSON(output)

While I agree it would be handy for %JSON.Adaptor to have a way to do this with one of their export methods, I think the intent may be to allow us to immediately take the JSON as a string to write it out to an HTTP Request body, which is where I use it most:

Set sc = pRequest.%JSONExportToString(.jsonPayload)
THROW:$$$ISERR(sc) $$$ERROR($$$GeneralError, "Couldn't execute object to json conversion") 
// Set HTTP Request Content-Type to JSON
Set tSC=..%HttpRequest.SetHeader("content-type","application/json") 
// Write the JSON Payload to the HttpRequest Body
Do ..%HttpRequest.EntityBody.Write()
S tSC = ..%HttpRequest.EntityBody.Write(jsonPayload)

Not sure what you're asking regarding the ID of the object. Which object? If you're referring to a persistent message class that extends %JSON.Adaptor, there is %Id() but I haven't used it so not sure if that's what you're after or not.

Yeah, I know that I can do %FromJSON, but it looks like overhead, here.

Look at this article, you can generate API implementation just from swagger specification. It generates a bunch of methods for each call in swagger spec.

Something like this

/// Get an article. Auth not required<br/>
/// The method arguments hold values for:<br/>
///     slug, Slug of the article to get<br/>
ClassMethod GetArticle(slug As %String) As %DynamicObject
{
    //(Place business logic here)
    //Do ..%SetStatusCode(<HTTP_status_code>)
    //Do ..%SetHeader(<name>,<value>)
    //Quit (Place response here) ; response may be a string, stream or dynamic object
}

So, in this method, I would add code like this

/// Get an article. Auth not required<br/>
/// The method arguments hold values for:<br/>
///     slug, Slug of the article to get<br/>
ClassMethod GetArticle(slug As %String) As %DynamicObject
{
    Set article = ##class(Article).slugOpen(slug,, .tSC)
    If $$$ISERR(tSC) {
        Do ..%SetStatusCode(404)
        Return 
    }
    
    Return article
    #; Or
    Return article.%JSONExport()
}

But this will not work. The only ways to make to work is to return string or stream

Return article.%JSONExportToString()
Return article.%JSONExportToStream()

But, I have to wrap the output. And the best would be to get something like this

Return { "article": (article.%JSONExport()) }

While I have to write this, and hope do not get MAXSTRING error, for some cases

Return "{ ""article"": " _ article.%JSONExportToString() _ "}"

And it's just only a case with one object, while for some cases I have to return an array.

Apologies as I haven't gotten into the whole Swagger generated API thing yet (working that direction though.) But to your desired output above, could you not do something like:

Set tRetObj = {}
Set tRetObj.article = {}.%FromJSON(article.%JSONExportToString())
Return tRetObj

Again, maybe I'm not fully understanding so I'll butt out after this reply and maybe someone else can help better. :-) I do see your concern re: MAXSTRING though and have encountered this concern myself. Though taking the export to string out of the return statement I think would allow you to handle that exception better.

This code works great however it creates an additional ID1 column because you calculate the ID using SQL. To remedy this you could also get the ID from SQL in the following way:

Property id As %Integer(%JSONINCLUDE = "OUTPUTONLY") [ Calculated, Transient ];

Method idGet() As %Status
{
    return ..%Id()
}

The getter will now provide the ID and Transient ensures no additional property is stored in the database.

And just a note, for one more issue. The description to method %JSONNew says, that I can pass JSON which will be imported to the just created object

/// Get an instance of an JSON enabled class.<br><br>
/// 
/// You may override this method to do custom processing (such as initializing
/// the object instance) before returning an instance of this class.
/// However, this method should not be called directly from user code.<br>
/// Arguments:<br>
///     dynamicObject is the dynamic object with thee values to be assigned to the new object.<br>
///     containerOref is the containing object instance when called from JSONImport.
ClassMethod %JSONNew(dynamicObject As %DynamicObject, containerOref As %RegisteredObject = "") As %RegisteredObject [ CodeMode = generator, GenerateAfter = %JSONGenerate, ServerOnly = 1 ]
{
    Quit ##class(%JSON.Generator).JSONNew(.%mode,.%class,.%property,.%method,.%parameter,.%codemode,.%code,.%classmodify,.%context)
}

But in fact, it does does nothing with it, and generated code, just returns new object

%JSONNew(dynamicObject,containerOref="") public {
  Quit ##class(Conduit.Model.User).%New()
}