MAXSTRING trying to read string from JSON object.
I receive JSON from a webservice and parse it to an object:
Set wout=0set 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.
Comments
@Nicki Vallentgoed
You can convert the HttpResponse.Data into a %DynamicObject without using an intermediate stream:
Set jsonob = ##class(%DynamicObject).%FromJSON(httprequest.HttpResponse.Data)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):
.png)
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.
Please note that jsonob.value.labReports is an array, I assume that it possibly can contains multiple pdfs, so an iterator is required.
You have right, I overlooked the [ character, sorry (usually one posts a piece of code and not a piece of picture!). So the above line would be
if jsonobj, jsonobj.statusCode = 200 {
for i=0:1:jsonobj.value.labReports.%Size()-1 {
set pdf(i)=jsonobj.value.labReports.%Get(i).%Get("pdf",,"stream<base64")
}
... // do something with pfd(i) streams
}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.
Thanks, the .%Get("pdf",,"stream<base64") works.
It is super frustrating though.
This produces an error: <PARAMETER> 127 %GetNext^%Iterator.Array.1
Set iter = jsonob.value.labReports.%GetIterator()
While iter.%GetNext(.key,.labReport,.type) {
w !,"type: "_type
}I do not know if it a bug on my side or the documentation:
.png)
Take a look to this article, maybe it's helpful:
https://community.intersystems.com/post/retrieving-base64-files-post-ca…