The initial value of x=14 in the above ugly example happened because I ran it 3 times while I has debugging and modifying it.  As the comment says, I originally did quit y+3 but I had to change it to quit x+3 because the second execution of the quit statement got an <UNDEFINED> *y signal before it got to the <COMMAND> signal.

As mentioned above, ObjectScript was originally based on ANSI MUMPS.  However, that ANSI standard has been retired but I believe there still exists an ISO M language standard which is identical to the ANSI M[UMPS} standard.  ObjectScript still supports the ancient MUMPS/M standard but InterSystems no longer documents many of the legacy features of MUMPS/M.

There is no warning for a QUIT with expression statement in a FOR loop because it is possible that a QUIT expr could be legal in a FOR loop and that legality/illegality cannot not be detected until run-time.

Here is a messy example containing both legacy M and modern ObjectScript.  It contains a quit x+3 statement that is legal when first encountered and illegal when encountered the second time.

InterSystems supports ugly legacy M features so customers do not need to rewrite ancient code.  New code should not look like this ugly example as avoiding certain legacy features is strongly recommended.

USER>zp
foo(a,b) 
         for i=1:1:10 {
         set x=$$foo2(a)  zw x
         goto foo3
foo2(y)  
foo3     set z=a
         zw a,x,y,z
         zw "about to quit y+3"
         quit x+3
         }

USER>zw $$foo(5,9)                    
a=5
x=14
y=5
z=5
"about to quit y+3"
x=17
a=5
x=17
z=5
"about to quit y+3"

 quit x+3
   ^
<COMMAND>foo3+3^foo
USER 6f2>

Note that <COMMAND> was signaled 3 lines after label foo3 and then we interactively get a prompt from the debugger.

BTW: Evgeny might ask strange questions but the properties of legacy M/MUMPS can be much weirder.
 

Steven Hobbs · Nov 25, 2025 go to post

I am going to assume that local variable 'identifiers' contains an oref referencing either the %DynamicArray or the %DynamicObject class.  If the 'identifiers' value is an oref that references some other class then ignore everything else in this reply.

Let us assume 'identifiers' refers to a %DynamicArray object (although a reference to a %DynamicObject would involve a similar discussion.)  Each entry in the %DynamicArray is a JSON value.  Those entries have one the following types:  "null", "boolean", "number", "oref", "object", "array" or "string".  One these type strings will be returned in variable 'type' specifying which JSON value was converted to an ObjectScript value that was returned in argument variable 'value'.  The 'type' "array" means the returned 'value' is a nested %DynamicArray reference; the 'type' 'object' means the returned 'value' is a nested %DynamicObject reference; and the 'type' "oref" means the returned 'value' is some other ObjectScript class object reference.  All other 'type' string values will mean that 'value' does not contain an oref.

The statement 'set type = value.%Get("type") is only legal in ObjectScript when 'type' contains the "array", "object" or "oref" type strings.  All other type strings will cause 'value.%Get("type") to signal an <INVALID OREF> error.  Most likely you want 'value' to be a reference to a nested %DynamicObject containing a JSON entry using the key name "type".

[[ If 'value' contains the "array" type reference then 'value.%Get(arg)' would expect 'arg' to be a number rather than a string.  If 'value' contains an "oref" type reference then 'value' would be a different ObjectScript oref which must reference a class object containing a method named '%Get'. And other types for the 'value' argument would signal <INVALID OREF> since the 'value.%Get("type")' syntax requires 'value' to an oref and the other 'type' possibilities are not oref values. ]]

The other statement 'set text = type.%Get("text")' will always signal <INVALID OREF> since %GetNext always returns a string value in the 'type' variable passed as the third argument.

So, let's assume 'identifiers' contains a JSON array where every entry in the array is a JSON object containing information about an identifier.  In this case you would know the type of each array element so you could iterate calling %GetNext(.key,.value), although if you did call %GetNext(.key,.value,.type) then immediately after the %GetNext call you could test for a data error with:
    if type'="object" { do $$$ERROR($$$GeneralError,"Invalid identifier object") }

You could then look at 'value."type"' and 'value."text"' to  get the 'type' and textual name from the JSON object describing an identifier.  [[ You do not need the %Get method when you are using a string literal as the key name of a %DynamicObject entry. Using 'value.%Get(key)' is only needed when 'key' is a run-time key name expression. ]]

Steven Hobbs · Nov 6, 2025 go to post

I seriously doubt it is a %Regex issue.  The ? appears when some interface is converting characters to one of the many 8-bit character set codes and the source character code has a character that is not in the destination character code.  This can happen to about 2**20 characters of Unicode when they are converted to any 8-bit code.  It can also happen when converting 8-bit to a different 8-bit code.

Your terminal emulator and the IRIS terminal device can both do such conversions.  A IRIS file device can also do such conversions.  Different platforms can use different default conversions which explains why some different people cannot reproduce the results of other people.

Steven Hobbs · Oct 30, 2025 go to post

You might be having problems with some of the unusual syntax of ObjectScript.

Like other languages created in the 1960s (E.g., FORTRAN IV and PL/I), ObjectScript does not have any reserved words.  The word 'if' can be a command if it appears where a command is specified; it can be local variable if it appears where an expression value is expected; it can sometimes be a global variable or program name if it appears after '^';  it can be a function name if it appears after '$$';  it can be a macro name if it appears after '$$$';  it be a routine or procedure name if it appears after the 'do' command; if can sometimes be a method or a property name if it appears after '.'; etc.

Your macros
    #define TestIf(%arr)    if %arr>0 QUIT 5          ;; legacy IF command
    #define TestIf(%arr)    if (%arr>0) {QUIT 5}     ;; more modern IF command
looks as if TestIf should be expanded where a command is expected so that it conditionally executes the command 'QUIT 5' which would exit the current function with a return value of 5.

However, your macro placement
    set a = $$$TestIf(3)
looks like $$$TestIf is going to be an expression containing a value for the right side of an assignment (a SET command).  It expands as
    set a = if 3 < 0 QUIT 5
which is a syntactically correct assignment of 'a' assigned the value of the variable 'if'  and that 'set' command is followed by a command named '3' which is bad syntax.

Your macro
#define logDebug(%arr) $SELECT(^GlFSL("Debug")>0:Entry^HS.Local.VA.Util.Log(%arr,,"D"),:QUIT)
looks like a macro  containing the built-in '$select' function and the macro should expand into conditional expression (as opposed to a macro containing the command 'if' which should expand in to a conditional statement.)  The first $select argument ^GlFSL("Debug")>0:Entry^HS.Local.VA.Util.Log(%arr,,"D") evaluates ^GlFSL("Debug")>0 and it that comparison is true, the $select evaluates Entry^HS.Local.VA.Util.Log(%arr,,"D") as the final value of the $select expression.  If the selector of the first argument is false then $select goes on the second argument ,:QUIT.  This has the syntax for the default value of a argument of the built-in $case function rather than be correct syntax for a $select argument.  For a $select you would write 1:QUIT which would return the value of the QUIT variable.  However,  I suspect you might have wanted a QUIT command which would terminated the current routine/procedure.  In that case you should write  the conditional commands
    if 
^GlFSL("Debug")>0 then { set temp=Entry^HS.Local.VA.Util.Log(%arr,,"D")} else {QUIT}
if you do not execute the QUIT command then you can continue executing the next command line using the conditionally set 'temp' variable.

Steven Hobbs · Oct 13, 2025 go to post

The JSON standard does not support nested, circular objects.  The IEEE Standards Association did call for a committee to be formed to extend JSON to include graphs of objects but there was not enough interest to form such a committee.

Steven Hobbs · Jul 24, 2025 go to post

ETNMINIM makes a very small ^ERRORS log with just 4 entries:  As *COM comment line, a *STACK,0,V,$H line containing the $HOROLOG system variable, a *STACK,0,V,$I line containing the $IO system variable, and a *STACK,0,V,$J line containing the $JOB system variable.  Normal ^ERROR logs created by ^%ETN usually contain 100s of entries, including many other entries other than *COM and *STACK..  Your Error Details displays Expression Value entries for the $H, $I and $J variables. and they all seem correct including a $H value of  67407,67118 which is a date of 07/21/2025 and a local time of  06:38:38PM.

The Error Details also displays a Process box containing "39452 01/06/1841 18 38-38 No: 82".  The job number (39452), time (18:38:38 ) and the Error ID (82) all seem correct but the Error Details seem to have incorrectly used the number 7 as the date value.

I have no experience with the Error Details display generated by the SMP web page.  Possibly that web page is decoding a *STACK entry (or some other *XXX entry) for the date that was not generated by ETNMINIM.  Since the %ETN logging failure was reported by error id 82 and there seems to exist an error id 81, it is possible that some useful information can be found by executing ZWRITE ^ERRORS(67407,81) in the LEARNING namespace.  Remember this particular ^ERRORS log entry was generated by a MERGE command that did not complete.

Steven Hobbs · Jul 22, 2025 go to post

If an <INTERRUPT> occurs outside of the ^%ETN routine then ^%ETN ignores the interrupt and THROWs it back to the application code.

In this case the <INTERRUPT> occurs inside the ^%ETN routine while it is processing an earlier error signal.  At the line in question, ErrST+6, the ^%ETN is executing the
    Merge ^ERRORS(date,errnum)=^mtemp(State)
command which is merging the error state (in ^mtemp(State)) into the ^ERRORS(date,errnum) log variable.  That could mean that ^ERRORS(date,errnum) is only partially created so it would contain corrupted information about the error trap sent to ^%ETN.
It looks like that Merge got held up for some reason allowing the user time to signal <INTERRUPT> with a ctrl-C, or some other <INTERRUPT> signal.  That would cause ^%ETN to KILL  ^mtemp(state) and then ^%ETN executes the following local routine which generates a minimum entry in the ^ERROR log global entry describing the error that aborted ^%ETN.

ETNMINIM
 N h,d,i
 S h=$H,d=$E(h,"^",1),i=$I(^ERRORS(d))
 S ^ERRORS(d,i,"*COM")=h_","_$ZE_"; log entry lost."
 S ^ERRORS(d,i,"*STACK",0,"V","$H")=h,^("$I")=$I,^("$J")=$J
 Q

This mimimum ^ERRORS(date,count) entry describing the signal that interrupted ^%ETN should just follow the ^ERROR(date,count-1) entry that was only partially generated.

Steven Hobbs · Jul 21, 2025 go to post

The zero date for the ObjectScript built-in $HOROLOG variable (aka $H) is 12/31/1840 so the date, 01/06,1841, you are seeing is date number 0 plus 7 days.  Now the $H day number in your Error Details, 67407,  is 07/21/2025 (i.e., today) and the $H time number (67118 seconds) is 06:38:38PM local time.

It looks like <INTERRUPT> was signaled while %ETN was generating the error log.  The <INTERRUPT> signal usually means someone typed control-C on the terminal.  The control-C may have interrupted %ETN before it had dumped all the date/time information of the original error.  The $H string in your Error Details, "67407,67118" is probably the date/time when the control-C was typed.

Steven Hobbs · Jul 7, 2025 go to post

Your error message says the %DynamicArray class does not have a %GetAt method.  It has never had such a method although back in Caché 2016.1 there was an experimental array class that had a $getAt method.

You might try replacing all your .%GetAt(0) method calls with .%Get(0) method calls.

Steven Hobbs · May 8, 2025 go to post

The %Stream.DynamicCharacter (%SDC) and %Stream.DynamicBinary (%SDB) classes are designed to hold a copy of the string data that is in an element of a %DynamicArray (%DA) or a %DynamicObject (%DO) class.  The %SDC class, and its %SDB subclass, were designed to be small and efficient even when the string in the %DA/%DO class is very large, containing many billions of characters or bytes.  The %SDC classes contain readonly data so you cannot modify the string data that they contain.  However, you can create any other object in %Stream package of classes that supports the CopyFrom() method (such the %Stream.TmpCharacter, %Stream.TmpBinary, %Stream.FileCharacter or %Stream.FileBinary classes) and use the CopyFrom() method to create a writeable copy of the string data in the %SDC object.  If your %SDC stream contains many billions of data elements then the
    DO tmpStream.CopyFrom(sdc)
access will take much longer to load the tmpStream object than it took to create the original sdc object.

A %DA/%DO object internally contains a data structure called a Packed Vector Array (PVA).  The PVA supports rapid creation from JSON text and also rapid data lookup.  It does not support rapid data modification, although the %Set() method can be used to modify and rearrange the data in a PVA.  When a %DA/%DO contains a string element longer than a selected length (currently defaulting to about 4095 characters) then those characters are placed in a separate buffer object and a small object reference (oref) is placed in the PVA.  The buffer classes are the %DynamicString, %DynamicBinary and %DynamicBase64 classes which are used to contain the characters and bytes of long strings.  These classes are not very interesting to ordinary ObjectScript programers as they have no properties and no methods.  When a %SDC object is created (using something like the SET sdc=dao.%Get(key,,"stream") ObjectScript statement) then the DAOData property in the %SDC object references the buffer class so that characters/bytes in the buffer do not need be copied.  A buffer class can be shared by multiple %DA, %DO, %SDC and %SDB objects, which is why the buffer objects are read only.

Steven Hobbs · May 2, 2025 go to post

The default 'mindate' value for the $ZDATE(hdate,dformat,monthlist,yearopt,startwin,endwin,mindate,maxdate,erropt,localeopt)
system function call is 0, which represents 1840-12-31.  However, you can supply your own 'mindate' argument with negative values down -672045 which does date conversions more distantly into the past.

USER>zwrite $zdate(-672045,3,,,,,-672045)  
"0001-01-01"

which is Gregorian date January 1st in the year 0001 CE.  (That is the first day, in the first month in the first year of the first century.)  I should note that the Gregorian calendar was not in use back in the year 0001 CE but I don't think IRIS has support for Julius Caesar's calendar.  Passing a 'mindate' argument value more negative than -672045 will get you an <ILLEGAL VALUE> signal from $ZDATE() because there is some debate about whether the previous year is 0000 CE, -0001 CE or +0001 BCE.

Steven Hobbs · May 1, 2025 go to post

$ZT is the abbreviation for the $ZTRAP system variable.  You learn more about it at https://docs.intersystems.com/iris20251/csp/docbook/Doc.View.cls?KEY=RC…;

Setting SET $ZTRAP="^%ETSDK" will call a routine name  ^%ETSDK but I have no idea what the routine is.  Maybe it is a special Error Trap routine used at your site.

Executing
    SET $ZTRAP="^%ETN"
in an ObjectScript routine will setup the ^%ETN utility routine as an error trap routine which will catch an error signal and then dump the process state into the namespace before terminating the process.  Later, executing the DO ^%ERN utility will allow you to examine the various error trap dumps in a namespace.

Steven Hobbs · Mar 17, 2025 go to post

If you are doing a moderate amount of computing between database access then there is no question that Python is faster then ObjectScript.  If you are doing heavy computing then a call out to C++ or C would be faster than both ObjectScript and Python.

But if you doing lots of database accesses with only simple string compares and string computations between each access then ObjectScript will usually be fastest because it is so close to the Globals engine that is doing all the accesses.

Generally, manipulation of strings in ObjectScript is reasonably good.  Parsing of JSON and XML is OK if you are using functions built into ObjectScript to do the parsing.  But if you are doing character-by-character parsing of complex data structures that are not supported by ObjectScript built-ins then it might be best to call out of ObjectScript to do that parsing.

Steven Hobbs · Mar 12, 2025 go to post

An aside: when you have two $LIST values, L1 and L2 ,that you want to concatenate then you can just compute L1_L2 and string concatenation will do the job.  Some programmers loop over L2 one element at at time and add that element to the end of L1.  This can take n-squared time based on some product of the $LISTLENGTHs of L1 and L2.  Using string concatenation just depends on the sum on the lengths of L1 and L2.

Steven Hobbs · Mar 12, 2025 go to post

Interestingly, for small examples, L<30, the first example is slower because it does both a $LISTBUILD plus a concatenation while the others just call a $LIST/$LISTUPDATE built-in.  But for large examples, L>1000, the first example can win by at least one order of magnitude because the other examples include execution steps that take O(L**2) time.

Steven Hobbs · Feb 25, 2025 go to post

You mention that you are using IRIS version 2024.1.  The 3 argument form of %GetNext first appeared in IRIS version 2024.3 so it is possible that HealthShare is already using the 3 argument %GetNext method.

Steven Hobbs · Feb 25, 2025 go to post

The %GetNext() method calls in the HS.FHIR.DTL.Util.JSON.Adapter class must have been written using 2 arguments and looking something like
   while iter.%GetNext(.Name, .Value) {

When the two argument form of %GetNext encounters a %String value, it always return an ObjectScript string in the 2nd argument which limits the string length to 3641144 characters.  However, there is a 3 argument form of %GetNext(.Name, .Value, .Type) which never signals <MAXSTRING>.  If a string element in a %DynamicArray/%DynamicObject (%DA/%DO) element is too long then a %Stream oref is returned in the .Value argument while the .Type argument variable contains the value "string".  If .Type is not "string" but is instead "oref" then that tells you the element in the %DA/%DO was originally a %Stream oref and not a %String value.  See the Class Reference web page describing the %Iterator.Object class which will explain how to use the 3rd .Type argument to solve ambiguities that occur when %GetNext must convert the %DA/DO value returned in .Value to an ObjectScript value different from the original JSON value.

You should suggest to HealthShare developers that their HS.FHIR.DTL.Util.JSON.Adapter class (and maybe some other classes) should be using the 3 argument form of %GetNext(.Name,.Value,.Type) instead of the 2 argument form %GetNext(.Name,.Value) in order to eliminate possible <MAXSTRING> and <MAXNUMBER> errors.

Steven Hobbs · Jan 16, 2025 go to post

Is the file you are editing in IRISLIB or some other Read-Only database?  If so, use the System Management Portal (SMP) to turn off the database Read-Only option.

Steven Hobbs · Dec 16, 2024 go to post

I am not sure exactly what you want to do.  Is check1 just an ordinary string, or might it sometimes be a string that starts with a number (like a %Status variable)?

The unary plus operator (+) turns a string operand into a number.  If the string starts with the syntax of a number then +string returns that number and ignores characters in the string following the numeric syntax.  If the string does not start with a number (this includes the empty string) then +string returns 0.  It is possible for +string to signal <MAXNUMBER> if the characters at the beginning of string are too large for conversion to the computational numbers supported by ObjectScript.

You could try
  ret:+check1 check1
which would return check1 only if it were a non-empty string that starts with a non-zero number.

If you want to return a string value that could be tested as a boolean that you have to append "1" to the front of the string if +string=0 is true.

Steven Hobbs · Nov 25, 2024 go to post

The Class Reference pages for IRIS, available in any IRIS installation, describe all the classes installed in the installation.  The Class Reference for the %Regex.Matcher class documents that the %Regex.Matcher class comes from the International Components for Unicode (ICU). The ICU maintains web pages at https://icu.unicode.org .  The class reference documentation also contains the following statement for ICU documentation specific to the IRIS %Regex:

{quote}The definition and features of the ICU regular expression package can be found in https://unicode-org.github.io/icu/userguide/strings/regexp.html .{quote}

Additional documentation on the Unicode Regex package specific to how it interacts with the Unicode character set can be found at https://www.unicode.org/reports/tr18/&nbsp;.

Steven Hobbs · Oct 15, 2024 go to post

A quick comment on performance.  A TRY ... CATCH is very efficient at run time if no exception occurs.  The only extra code that is executed is a jump at the at end of the TRY block to go around the CATCH block.  However, TRY ... CATCH is slower than using the %ZTRAP when an execption does occur.  At compile time TRY ... CATCH blocks build tables that include program counters that describe which object code locations are in each TRY block and where the corresponding CATCH block begins.  When an error is signaled (or THROWn) at run time, it is necessary to scan the frame stack searching the compile-time generated TRY ... CATCH tables to find the CATCH location to go to after popping the intermediate stack frames.  If there are no TRY ... CATCH blocks involved then it is usually faster to just jump to the most recent setting of the $ZTRAP variable.

When exceptions are "exceptional"  then TRY ... CATCH is most efficient since it avoids pushing $ZTRAP value in preparation for handling an exception and it also avoids popping the $ZTRAP value when the exception does not occur.  If exceptions are part of a usual, successful, and often executed code path then it might be better to use $ZTRAP to catch those frequently executed signals.

Steven Hobbs · Oct 8, 2024 go to post

Upgrading from an 8-bit instance to  a Unicode instance is much simpler as you can just skip your export and import steps.  Instead, just reinstall your original IRIS kit as an update kit.  During the update, the installation will ask you:

Do you want to convert 8-bit to Unicode <No>?

Just answer Yes and the instance will be converted.

Whenever a string value in a ^global variable contains only 8-bit characters then a Unicode IRIS instance stores that string in IRIS.DAT using 8-bit representation in order to save space.  After the update, all your existing global data items are still there and the strings are all in 8-bit.  IRIS Unicode instances use the UTF-16 Unicode encoding.  If you have any 8-bit strings encoded in UTF-8 then you can use $ZCVT to convert UTF-8 strings to the IRIS default Unicode representation which uses UTF-16.  Functions like $wlength, $wextract, etc. do not work on UTF-8 encoded 8-bit strings but they do work on the UTF-16 encoded strings.

Note, if you do port IRIS.DAT files between different hardware instances and you also port between big-endian hardware and little-endian hardware  (e.g., aix to windows) then there is a documented utility that describes how to convert the IRIS.DAT files between big-endian and little-endian representation.

There is no support for automatic conversion starting from a Unicode IRIS.DAT file back to an 8-bit IRIS.DAT file.  You can imagine this working if you are very lucky and the ported Unicode IRIS.DAT files just happen to have no Unicode strings, which will not happen with the "%SYS" namespace because the upgrade will add Unicode support to that namespace which will include some Unicode strings.  With only a few, easily found Unicode strings then you can use %ZCVT to convert UTF-16 to 8-bit UTF-8.  If are so lucky that you can do those conversions to completely remove all UTF-16 strings from a IRIS Unicode instance then you can try to install a new 8-bit instance and keep the IRISSYS and IRISLIB databases and replace the other database files with IRIS.DAT files that now just contain 8-bit user string data.  If you fail to convert all the Unicode strings while trying to go back to an 8-bit instance then I believe you will get a <WIDE CHAR> signal if you attempt to access wide UTF-16 data.

Steven Hobbs · Sep 26, 2024 go to post

You cannot store {{}} as it is an object reference (oref) and the translation of the oref to a %String is not useful.  You can try:

Parameter STATUSES = {{}}.%ToJSON();

and where you write 'w ..#STATUSES' you can instead write 'w [].%FromJSON(..#STATUSES)'

I assume you can also write 'w [].%FromJSON(##class(Test...).#STATUSES)' although I am not certain that will do what you desire.  [[ There is an ancient request for "DWIM" software (Do What I Mean software) to replace "DWIS" software (Do What I Said software). ]]

And of course you Parameter modification can be written as:

    d $system.OBJ.UpdateConfigParam("Test","STATUSES",{"a":10}.%ToJSON())

Steven Hobbs · Aug 13, 2024 go to post

The %DynamicAbstractObject (%DAO) subclasses, including the %DynamicArray and %DynamicObject classes, provide the best way to read/send JSON representation into/out-of IRIS databases.

The %FromJSON method can create a tree of %DAO objects from external JSON representation.  Then elements of %DAO data can be read or accessed using ObjectScript property syntax and using methods like %Get and %GetIterator.

Data inside an IRIS database can be used to build a tree of %DAO objects by using the ObjectScript JSON constructor syntax, property assignment statements and using methods like %Set and %Push.  If all you do is store values into previously undefined elements of a %DAO object then the performance is usually quite good.  Then that %DAO tree can be turned into JSON representation using the %ToJSON method.

It is not recommended that data in a tree of %DAO objects be extensively and repetitively modified/updated by the %Set method or by other ways that can modify data in a %DAO.  This is especially true if you are repetitively changing the size of data elements.  In such cases, the data in %DAO objects should be extracted and converted to some other optimized database structure.  Often this can be Relational DataBase access if a mix of rectangular arrays provides the desirable access.  It can be a %Stream if sequential access is needed.  If a number of data elements are to be searched then columnar vectors can be used.  IRIS can supply all these other access methods by layering them on top of ObjectScript multi-dimensional arrays.  The IRIS multi-dimensional arrays (both global and local) provide the best tradeoffs across a large variety of differently ordered read/write/update/modification operations.

If you have data in a %DAO tree and you need to make small or infrequent modifications of the data then it is acceptable to leave it in %DAO representation.  You can also make repetitive reads of %DAO data without needing to convert to some other representation.  But otherwise, %DAO objects are best limited to moving data into and out-of JSON representation.

Steven Hobbs · Jul 25, 2024 go to post

Assuming  that response.message_code is part of an ObjectScript expression then you want to evaluate response."message_code" because _ is an ObjectScript operator so you must quote "message_code" to change it into a method or property name.  Otherwise, _ is the string concatenation operator so the value of response.message will be concatenated with the value of code.

Steven Hobbs · Jul 16, 2024 go to post

I agree that LOG^%ETN, and ^%ETN as a process terminating trap routine, are the best way to log problems.  But we should also mention that DO ^%ERN is the utility that will dump the data that LOG^%ETN places in the ^ERRORS multi-dim global.  That dump includes state information, a complete call-stack dump, a list of all active local variables at each stack level, and in recent IRIS releases, it also includes a dump of all Class Objects with an oref active in memory.  This is a lot of information for each ^ERRORS entry so you might not want to be over-enthusiastic with your calls on LOG^%ETN.

Steven Hobbs · Jul 15, 2024 go to post

I don't know anything about the FhirBinary class but I do know why the %GetNext method in the %Iterator.Array or %Iterator.Object classes is giving the <MAXSTRING> signal.  Let's assume the %GetNext in question is accessing a %DynamicObject as the %DynamicArray case is identical.  A %DynamicObject (usually created by the %FromJSON method) limits it contained string elements only by the amount of memory that can be obtained from the Operating System running the IRIS instance.  Fetching a long string element from a %DynamicObject as an ObjectScript string value will signal <MAXSTRING> when the %DynamicObject string element is too long.  However, you can fetch a long string element as a %Stream.DynamicBinary or %Stream.DynamicCharacter class object.  Such a %Stream object can contain any string that will into the system memory.  Long %Stream contents longer than the ObjectScript maximum string length can be accessed by using the Read(.len,.status) method to access smaller substrings of the %Stream contents.  Also, you can copy one class of long %Stream into a different class of long %Stream if you want to move a long string out of memory and into either a a file or an ObjectScript global array.

The Class Reference documentation for the %Iterator.Object class does not directly appear in either Class Reference webpage on an IRIS instance nor in the Class Reference web page in the InterSystems IRIS network documentation.  This strikes me as a documentation bug.

Fortunately you can see the appropriate Class Reference documentation by indirectly going to the Class Reference page for the %Library.DynamicObject class (or %Library.DynamicArray class) and going to the documentation for %GetIterator method. That documentaton contains a link to the %Iterator.Object class. Click on that link and you will go the %Iterator.Object Class Reference page where you can see the documentation for the GetNext(.key,.value,.type) method.  

%GetNext method is used to iterate over the values in a %DynamicObject.  The third argument, .type, is optional.  The two argument form, %GetNext(.key,.value) will return the key index in .key and its associated ObjectScript value in .value.  If the element value cannot be converted to an ObjectScript value then you will get a <MAXSTRING> or <MAXNUMBER> signal.  However, the three argument form, %GetNext(.key,.value,.type) will not signal these errors.  In place of a <MAXSTRING> signal, the .type argument will contain "string" and the the .value argument will contain the appropriate in-memory %Stream object value.  In place of a <MAXNUMBER> signal, the .type argument will contain "number" and .value will contain a string with the decimal representation of the numeric value.

More detail of other uses for the .type argument of the %GetNext(.key,.value,.type) method call can be found on the %Iterator.Object (or %Iterator.Array) Class Reference web page.

When the Fhir.Binary class needs to iterate over the elements of a %DynamicArray/Object then it should not use the 2 argument form of %GetNext(.key,.value) but should instead use the the 3 argument form %GetNext(.key,.value,.type) and be prepared when the type of the the .value variable does not match the type specified by the .type argument.

Steven Hobbs · Jun 25, 2024 go to post

We need to see more of your code to see what is truly happening.  The routine method call Do obj.%Set("data",pStream,"stream>base64") should have no problem providing your system has enough memory to construct entire 'obj' Dynamic Object in your process memory.  Your system will want to avoid having many large Dynamic Objects in memory at the same time.  If the system runs out memory for all the active processes then the operating system will usually start aborting processes randomly until the operating system decides it now has enough free memory to continue execution.

Calling %Set(,,) to read JSON text from a very large %Stream should only use moderate size buffers on the string stack.  If a call on %Set(,,) writing from a %Stream generates a <STRINGSTACK> signal then that should be reported to InterSystems as a bug.

However, your reply mentions the %ToJSON() function method call.  Calling %ToJSON as a function will place the result on the String Stack so it is possible such a function call could signal a <STRINGSTACK> error although if the %ToJSON() function call appears in a very simple expression then I would expect a <MAXSTRING> signal instead of a <STRINGSTACK> signal.

Calling %ToJSON() or %ToJSON(outstrm) as a routine method call in a DO statement should not get either a <STRINGSTACK> or a <MAXSTRING> signal since the result is not going to an ObjectScript string value.

Steven Hobbs · Jun 24, 2024 go to post

The %FromJSONFile(file) method does not take a %File object as an argument but instead takes a string containing the name of file.  You need to replace

set newObject = {}.%FromJSONFile(file)

with one of

set newObject = {}.%FromJSONFile("/tmp/longObjectFile.txt")
set newObject = {}.%FromJSON(file)

Originally, %FromJSON(input) would accept the 'input' argument as a %File object, a %Stream object, a JSON text string or a filename text string.  However, accepting an argument string as either JSON or a filename was considered to be security issue because a malicious user could be requested to enter a json-string but, being malicious, the user would instead enter a filename-string, or vice-versa.  As a result %FromJSON no longer accepts filename strings while %FromJSONFile only accepts filename strings.

No longer an issue, but it would be possible that legal VMS filename syntax could also be legal JSON text string syntax and such an argument string might be ambiguous on a VMS system.  But IRIS no longer supports VMS and Caché for VMS did not support Dynamic Objects.