Question
Raj Singh · Jun 9, 2020

Creating JSON objects from ObjectScript objects

Say I have an ObjectScript object called Book. It has 2 properties title and author. It extends JSON.%Adaptor, so I can call book.%JSONExport() and get this output:

{ "title": "For Whom the Bell Tolls", "author": "Hemmingway" }

In my web service I want to have a search function that returns an array of Books along with the total number of responses, like this: 

{
    "results": 3,
    "items": [
        { "title": "For Whom the Bell Tolls", "author": "Hemmingway" },
        { "title": "The Invisible Man", "author": "Ellison" }, 
        { "title": "Tender is the Night", "author": "Fitzgerald" }
    ]

}

What's the best way to do this? Can I export an ObjectScript object into a Dynamic Object?

00
3 0 5 410
Log in or sign up to continue

%JSON.Adaptor does not construct an intermediate dynamic object. You can use %ObjectToAET to convert normal object into dynamic object:

set dynObj = ##class(%ZEN.Auxiliary.altJSONProvider).%ObjectToAET(obj)

There are two approaches:

1. Create a "result set" class to hold the results (interestingly, InterSystems provides %XML.DataSet and other tools for this specific use case with XML/SOAP. Docs):

Class Test.JSONRS Extends (%RegisteredObject, %JSON.Adaptor)
{
Property count As %Integer;
Property results As list Of Book;
}

2. Simple approach:

  • Output header {"results": 3, "items": [
  • Call %JSONExport on each book (don't forget the comma at the end)
  • Output footer ]}

Despite being hacky, the second approach is better:

  • If JSON export on each individual object is successful it works every time and if some object fails we won't get valid a JSON anyways
  • It can be easily generalized to use with any type of result
  • It does not hold a large structure in memory as each object is loaded/displayed individually.

That said, I generally recommend against supplying count in query results because of many reasons:

Hi Raj,

I just tried answer for your question, I have given the code  below. I hope it helps you.

---------------------------------------------------------------------------------------------------------------------------------

   set array=[]
   set obj = {}
   set obj.title="For Whom the Bell Tolls"
   set obj.author="Hemmingway"
   do array.%Push(obj)    

   set obj = {}
   set obj.title="The Invisible Man"
   set obj.author="Ellison"
   do array.%Push(obj)    

   set obj = {}
   set obj.title="Tender is the Night"
   set obj.author="Fitzgerald"
   do array.%Push(obj)    

 set arraylen=0 
 set iter = array.%GetIterator()
   while iter.%GetNext() {
      set arraylen=$I(arraylen)
   }
   
   set MainObj={}
   set MainObj.results=arraylen
   set MainObj.items=array
   
   MainObj.%ToJSON()
----------------------------------------------------------------------------------------------------------------

Output:

{"results":3,"items":[{"title":"For Whom the Bell Tolls","author":"Hemmingway"},{"title":"The Invisible Man","author":"Ellison"},{"title":"Tender is the Night","author":"Fitzgerald"}]}

--------------------------------------------------------------------------------------------------------------------

Just for the sake of variety, (and to advertise the flexiblity of IRIS) i might suggest another way to consider the problem might be to use a SQL query that projects the %Persistent objects into a document format without the need for a JSON adapter

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RSQL_jsonobject

or, to consider the %DocDB storage model which would get you this behavior "for free" by storing the %persistent objects natively in JSON format.
 

Thanks all. I was using solution #1 from @Eduard Lebedyuk -- creating a new %RegisteredObject class to hold the response -- because it gives me good control over what is happening while keeping my code easy to understand. The solution using the `%ZEN.Auxiliary.altJSONProvider` class is interesting to keep in mind, but it outputs the class name as a "_class" key , which I don't want, and relying on the old %Zen class library isn't recommended in new solutions.

The answer from @Muni Ganesh works too, but I wanted to do something more elegant than the  "brute force"  approach (but that offers complete control over the process). 

@Max Abrahams you offer an interesting solution too. When I have an opportunity I'll have to try out that SQL-centric approach.

Here is a ready-made example (works even in last Caché):

Class dc.test Extends %Persistent
{

Property title As %VarString;

Property author As %VarString;

/// do ##class(dc.test).test()
ClassMethod test()
{
  &sql(truncate table dc.test)
  
  &sql(insert into dc.test(title,author)
    select 'For Whom the Bell Tolls','Hemmingway' union
    select 'The Invisible Man','Ellison' union
    select 'Tender is the Night','Fitzgerald')
  
  set provider=##class(%ZEN.Auxiliary.altJSONSQLProvider).%New(),
      provider.sql="select title,author from dc.test",
      provider.arrayName="items",
      provider.maxRows = 0,
      provider.%Format "tw"
  
  do provider.%WriteJSONStreamFromSQL(.stream,,,,,provider)
  
  set json={}.%FromJSON(stream),
      json.results=json.items.%Size()

  write json.%ToJSON()
}

}

Result:

USER>do ##class(dc.test).test()
{"items":[{"title":"For Whom the Bell Tolls","author":"Hemmingway"},{"title":"The Invisible Man","author":"Ellison"},{"title":"Tender is the Night","author":"Fitzgerald"}],"results":3}