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. ]]

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.

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.

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.

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.

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.

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.

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.

$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=RCO... 

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.

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.

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.

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.

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.

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/ .

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.