Question
· Jan 24

MAXSTRING trying to read string from JSON object.

I receive JSON from a webservice and parse it to an object:

Set wout=0
set stream=##class(%Stream.TmpCharacter).%New()
For {
	Quit:(httprequest.HttpResponse.Data.AtEnd || wout)
	d stream.Write(httprequest.HttpResponse.Data.Read(32000,.tSC))
}
Set jsonob={}.%FromJSON(stream)

jsonob contains a sub entity "pdf" which is a base64 string and %GetTypeOf() tells me it is a string.
However I am unable to read the nested jsonob.pdf into a stream or anything else as any attempt to use it result in a MAXLEN error.

Product version: IRIS 2021.1
$ZV: IRIS for UNIX (Red Hat Enterprise Linux for x86-64) 2021.1.1 (Build 324U) Thu Feb 24 2022 13:20:39 EST
Discussion (9)5
Log in or sign up to continue

There are two places where a <MAXSTRING> signal could give you problems with JSON stored in a %DynamciAbstractObject.  It can occur if the input is too long and it can occur when evaluating a JSON element that is too large.

The %FromJSON(stream) method can read JSON syntax directly from a stream of any length providing the operating system will give you enough virtual memory to hold the %DynamicAbstractObject.  You seem to already understand this although you don't have to make a copy of an existing %Stream value (although it might need a stream.Rewind() call before you pass the %Stream to %FromJSON.)

Evaluating dynobj.%Get("pdfkey") can give you a <MAXSTRING> signal if the "pdfkey" element of the %DynamicObject referenced by the oref in the local variable 'dynobj' is very long.  But look at the class reference description of the %Get method in the %DynamicObject class and you will see that the %Get method can have optional 2nd and 3rd parameters.  The value of dynobj.%Get("pdfkey",,"stream") will return a %Stream containing the characters of the "pdfkey" element without generating a <MAXSTRING> signal.  Even better, the value of dynobj.%Get("pdfkey",,"stream<base64") will be a %Stream containing the "pdfkey" element decoded out of the base64 encoding.

If you want to iterate through all the elements in a %DynamicObject then you can use the dynobj%GetIterator() method to create a %Iterator.Object class object.  The %GetNext(key,.value,.type) method of the %Iterator.Object class can assign a %Stream to the .value parameter when the selected "key" element contains a long string value *providing* the optional .type parameter is also present.  The .type parameter receives the %GetTypeOf value of the original object element so you can tell the difference between a long "string" type element returning a %Stream versus an "object" type element that contains a %Stream oref value.

My code:

try{
  Set jsonob = ##class(%DynamicObject).%FromJSON(httprequest.HttpResponse.Data)
  w !,"statusCodes: "_jsonob.%Get("statusCode"),!
  Set iter = jsonob.value.labReports.%GetIterator()
  While iter.%GetNext(.key, .labReport) {
  w !,"reportName: "_labReport.%Get("reportName")
  Set pdf=labReport.%Get("pdf",,"stream<base64")
  w !,"pdf: "_pdf.Read(20),!
  }
} catch(tException) {		
  w !,tException.DisplayString(),!
}

JSON looks like this( pdf the offending big string):

Output:

statusCodes: 200

reportName: Long-Oeloff-Badenhorst.pdf
<MAXSTRING> 5 ztestpdf+36^Test.Testy.1

ztestpdf+36 = Set pdf=labReport.%Get("pdf",,"stream<base64")

Two notes to your (above) code

- first, if you use the iterator method on an object which can contain long strings (longer what IRIS can handle) then you must specify the thrid argument to the %GetNext() method too, i.e.

while iterator.%GetNext(.key, .value, "stream") { ... }

- second, if you know the name and location of a stream property in an JSON object, like in your case, then just grab the data without using an iterator:

try { set jsonobj = {}.%FromJSON(httprequest.HttpResponse.Data) } catch { jsonobj=0 }
if jsonobj, jsonobj.statusCode = 200 {
   set pdf=jsonobj.value.labReport.%Get("pdf",,"stream<base64")  // according to the picture you provided
   ... // do something with the pdf-stream
}

For another example, how to work with long strings see this thread.

The third argument to %GetNext is not a string value.  It is a variable passed by reference:  iterator.%GetNext(.key,.value,.type).  When this returns then the variable type will contain a string value like “string”, “number”,”object”, etc.  If type contains “string” and $isobject(value) is true then value is a %Stream object containing your long string.