Dmitry Maslennikov · 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
    Quit "{ """ _ kind _ """: " _ output _ "}"


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

1 521
Discussion (6)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.

Thanks, @Craig.Regester. This is the correct solution to this use case. 

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

To be able to export %Id() as id property in JSON, I had to add this.

Property id As %Integer [ Calculated, SqlComputeCode = { Set {*} = {%%ID} }, SqlComputed ];

I think, that it would be better to have some parameter in a class which would enable to output id easier without hacks.

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()