%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?
Comments
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
}
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 tRetObjAgain, 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.
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()
}