Some additional picky details:

The unary + operator is a numeric operator so it converts its operand to a number.  If a string operand starts with a fractional number then unary + produces a fractional number (in its canonical numeric form) and it throws away any unneeded characters.  If you want your numeric result to be an integer then you need to throw away the fractional digits by doing an integer-division by 1.  Since the integer-division operator, \, is a numeric operator it always converts its operands to numbers so you no longer need the unary + to do the conversion of a string to numeric representation.  E.g.s:

USER>w "0012.543000abc"   
0012.543000abc
USER>w +"0012.543000abc"
12.543
USER>w +"0012.543000abc"\1
12
USER>w "0012.543000abc"\1 
12
 

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.

If your method can handle %Stream data then you can copy the large string piece-by-piece into an appropriate %Stream.  If your method cannot handle %Stream data then you must modify it to test for a parameter that is a %Stream object instead of a string and then process that data by calling the stream.Read() method to scan through the %Stream.

A few built-in methods do have this capability.  For example, the %FromJSON(arg) method in the %DynamicAbstractObject class will accept 'arg' being either a string or a %Stream object.

Well, my DO command suggestion won't work in ObjectScript because ObjectScript has extended the MUMPS DO command to accept syntax for the
   DO { code } WHILE expr,expr,...
statement.  The code block in the DO-WHILE extension does not assume the code block opens a new variable scope.

However, like most languages designed in the 1950s and 1960s, (including FORTRAN, BASIC and PL/I, but not C) MUMPS does not have reserved words.  Therefore, any newly added command can start with a new command word.  So we could have BLOCK { code } or SCOPE { code } as new commands that open a new variable scope.  We might be able to find a better command word than BLOCK or SCOPE.  We could even consider NEW { code }, although it might be considered confusing having an additional NEW command (one with a curly brace code block along with one with no arguments, one with list of argument names and one with a list of names surrounded by a pair of round braces).

I always wanted to extend argumentless  DO so that you could get a new scope without needing the fairly ugly dot-indentation lines.  Then you write something like

Do {
   New $roles
   Set $roles=""
   If ($extract($roles,1,$length("%All"))="%All") Set tHaveAllRole = 1
}

You could even write the whole thing on one line like

Do {New $roles  Set $roles=""  If ($extract($roles,1,$length("%All"))="%All")  Set tHaveAllRole = 1}

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.

You should remember that the types defined in the formal argument list of a Method declaration or defined when a local variable uses the #dim preprocessor directive are only advisory types.  Any ObjectScipt value or oref can passed as an actual arugment to a method.  A local variable specified in a #dim directive can contain any ObjectScript value or oref--not just the type/class specified in the #dim directive.

The type of a Property name is only checked when the %Save method (or other appropriate method) is applied to an object containing that Property.  While the object is active in memory you can assign any ObjectScript value or oref to that property.  You can choose to write you own specific PropertySet/PropertyGet methods containing code that applies your choice of run-time type checking and conversions during the modifications/evaluations of Property names.

The ObjectScript language has type-less variable contents and the types in the Class language extensions to ObjectScript are only enforced by run-time executed methods.

But of course, the Classes in the %Dictionary Package are useful for writing run-time methods that enforce the declared types of Properties.

Internally, a %DynamicArray or %DynamicObject is usually limited only by the amount of memory that the OS is willing to allocate to the job,  Because the components of such objects are not limited by their size, some very large components need to be allocated by a non-standard allocator that does not participate with the $ZSTORAGE limit.  But some components of a %DynamicObject/Array are allocated by the usual memory allocator so %ZSTORAGE might limit the size.

Since %DynamicObject/Array classes are always stored in memory (I once built one that needed 20GB of memory) you want to limit how many large objects are active at the same time.  When you encounter a large %DynamicObject/Array you want to move its data to a file or global %Stream before you start reading in another large %DynamicObject/Array,

The %FromJSON(…) method can create a %DynamicObject/Array while reading JSON from any kind of %Stream.  The %ToJSON(…) method can write JSON text to any kind of %Stream.  A %Stream is not usually limited by the max supported length of an ObjectScript string and the %FromJSON and %ToJSON methods read/write %Stream data using buffers smaller than the max supported string length.

If you have a %DynamicObject/Array with a string component larger than the ObjectScript max string length then you can use the three argument %Get(key,default,type) method where the ‘type’ argument specifies a “stream” type.  The resulting %Stream is still stored in memory but that %Stream is readonly and will often share the memory allocation with string component in the %DynamicObject/Array. That in-memory %Stream can be copied into a file or global %Stream.  The three argument %Set(key,value,type) method can create a %DynamicObject/Array component of any length when the ‘type’ argument specifies that the ‘value’ argument is a %Stream.  If the %Set ‘value’ argument is a readonly, in-memory %Stream created by %Get(key,default,”stream”) then the component created by %Set will usually share in-memory data with its  ‘value’ argument stream.

See the class reference documentation for %Get and %Set.  The ‘type’ argument options include the capabilities to encode to, or to decode from, BASE64 representation.  This can be useful when working with a BASE64 encoded .pdf file in a JSON object.

The numeric values inside a %DynamicObject can be a JSON number,  an ObjectScript Decimal Floating-Point number, an IEEE Binary Floating-Point number or an ObjectScript string containing the characters of an ObjectScript numeric literal.  The ObjectScript Decimal and the IEEE Binary numeric types do not keep track of trailing zeroes.  The JSON numbers do keep track of the trailing zero.  A JSON number or an ObjectScript string containing a numeric literal will loose their trailing zeroes as soon as an ObjectScript arithmetic expression uses those values as an operand.

Creating a %DynamicArray with the square-bracket syntax, [ ], or creating a %DynamicObject with the curly-bracket syntax, { }, will do parsing using JSON syntax which keeps the trailing zeroes.  However, if an ObjectScript expression is using the [ ] or { } syntax and an enclosed value is further enclosed inside round-bracket syntax then round brackets will enclose a single ObjectScript run-time expression which will be parsed using ObjectScript syntax rules.  (See json3 variable below for an example.)

If you evaluate a %DynamicObject element containing a JSON number with either ObjectScript property evaluation or with a simple ObjectScript %Get("propName") method evaluation then you get an ObjectScript Decimal value (or maybe an IEEE Binary value.)  E.g.:

USER>set json2 = { "decimal": 12.000, "decimal2":12.000E150}     

USER>zwrite json2                                                
json2={"decimal":12.000, "decimal2":12.000E150}  ; <DYNAMIC OBJECT>

USER>write json2.decimal,!,json2.%Get("decimal"),!,json2.decimal2
12
12
12000000000000000496000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Note that the JSON 12.000E150 was converted to the IEEE Binary value $DOUBLE(12.000E150) which is written containing the digits "496" as the 18th, 19th and 20th significant digits.  This occurs because of the rounding that occurs when doing the default conversions in both directions between binary floating-point values and decimal textual values.

[[ In case you want to see the fully accurate value of $DOUBLE(12.000E150), without any rounding modifications, the decimal value would be:
USER>write $FNUMBER(json2.decimal2, "G", 152)
12000000000000000496865878364855385636201248056368359870735338409167261627934342176354867336792577149743475436957677626049325994240678780913402110803968.0
which has 151significant decimal digits in its fully accurate representation in decimal. ]]

A more complex %Get method evaluation, %Get("decimal2", , "json") , will return a string containing the JSON syntax representation of the element.  E.g.:

USER>set json3 = { "decimal2":12.000E150, "OSnumber":(.12)}                                

USER>write json3.%Get("decimal2", , "json"),!,json3.OSnumber,!,json3.%Get("OSnumber", , "json")
12.000E150
.12
0.12

Note that .12 is an ObjectScript numeric value, while the strings 12.000E150 and 0.12 are syntactically legal JSON values.

It would be nice if the  %Set(key,value,type) method of the %DynamicObject class accepted "json" as a supported 'type' argument value, *BUT* it does not.  That would make it possible to add a JSON syntax numeric literal to an existing %DynamicObject using
    DO json3.%Set("decimal2", "12.000E150", "json")
Maybe someone should suggest such a feature extension to the %Set method.

The ##class(%DynamicAbstractObject).%FromJSON(s) class method, where 's' is a %String containing a JSON array/object or where 's' is a %Stream containing a JSON array/object, will parse the input JSON.  If the parse is successful then it returns an object reference of either %DynamicArray or %DynamicObject classes.  If the parse is unsuccessful then it will signal an error.

The parsing done by the %FromJSON class method is forgiving of at least one common syntax error.  The input string "[0.1,.1]" is not legal JSON because the second array number does not have a digit before the decimal point.  The %FromJSON class method will accept this input and silently supply the missing 0 digit.  If you apply the %ToJSON() method to the resulting %DynamicArray object then the result will be the string "[0.1,0.1]", which is legal JSON syntax.

There is also a %FromJSONFile (filename) class method which will accept a JSON array/object from a file with the specified 'filename'.

On Caché and IRiS, both %ZU(0) and $ZU(1) will signal <FUNCTION> because they need more than 1 arugment.

On IRIS, $ZU(0,directoryName,....) with enough additional arguments will create an IRIS.DAT database file. and $ZU(1,...) will modify an existing IRIS.DAT database file.  On Caché these functions create/modify a CACHE.DAT database file.  These are legacy functions.

The Config.Database class provides ways to modify the CPF configuration file to create and configure databases.

Interactively you also use the System Management Portal (SMP) by clicking SystemAdministration > Configuration > SystemConfiguration > LocalDatabase to find the SMP webpage that can create or modify local databases.

Or you can use the DO ^|"%SYS"|DATABASE command line utility as a way to create and configure databases.

InterSystems changed how <MAXNUMBER> was signaled during conversion from text representation to numeric representation when support for 64-bit IEEE binary floating point was added to Caché.  Textual numbers that overflowed the default ObjectScript decimal representation are converted to 64-bit IEEE binary floating point which supports a much larger range of magnitudes (but about 3 fewer digits of decimal precision.)  When a literal would exceed the magnitude of IEEE floating point, the choice of whether to signal <MAXNUMBER> depends on the run-time $SYSTEM.Process.IEEEError setting since IEEE floating-point overflow can either signal <MAXNUMBER> or it can return an IEEE floating-point infinity.  When the compiler sees a numeric literal that exceeds the finite IEEE number range then the decision to signal an error is delayed until run-time execution so the current $SYSTEM.Process.IEEEError setting can be obeyed.

The quoting of the slash character, "/", is optional in JSON.  When doing a $ZCVT output conversion ,"O", in JSON IRIS chooses to not quote the slash character.  It makes the output easier to read.  However, $ZCVT doing an input conversion, "I", will recognize a quoted slash character in JSON input.  E.g.:

USER>w $zcvt("abc\/def","I","JSON")
abc/def
USER>w $zcvt("abc/def","I","JSON") 
abc/def
 

A future release of IRIS will restore the ability of %DynamicArray and %DynamicObject classes to dispatch method calls on the method names of the form "propertyGet" and "propertySet".  However,  $PROPERTY(dynobj,"Id") should be more efficient than $METHOD(dynobj,"IdGet") so we recommend using $PROPERTY over using $METHOD when working with %DynamicObject class objects.  When the property name is known before execution time then dynobj.Id is the best way to do a simple property access of a %DynamicObject element with key name "Id".

A change was made to how IRIS dispatched method calls because there existed a few unusual cases where there was no property named "property" but the programmer was trying to dynamically dispatch to a method with the name "propertyGet" and the result was an undefined property error rather than a method call.  The change was made so that a method named "propertyGet" could always be correctly called when there was no property named "property".

Every possible string is a legal %DynamicObject key name and if the key name element is not otherwise defined then that key name element contains the empty string value.  The implementation of the %DynamicObject class does not include a list of all legal property names since all key name strings are legal.  The new way of dynamically dispatching method calls with names like "propertyGet" would first check if the property named "property" existed before attempting a property dispatch instead of method dispatch.  Since a %DynamicObject class object did not contain the name "property" in the list of legal property names and did not contain a method called "propertyGet" in the list of legal method names, the result of evaluating $METHOD(dynobj,"propertyGet") was a <METHOD DOES NOT EXIST> signal.  The future release of IRIS will have $METHOD(dynobj,"propertyGet") dispatch a property access rather than attempting a dynamic dispatch of a method access.

I should point out that "%" is a legal key name string but $METHOD(dynobj,"%Get) did not fetch (and will not fetch) the element with key name "%" because a %DynamicObject does contain a method named "%Get".  A call on a defined method name always occurs before attempting a dynamic method dispatch.

See the documentation on the

methods which can be used with subclasses of the %RegisteredObject class.

Another translation is

 Read *R:20
 ;; Test error case

 If '$Test { Use Write !!!,"Expired time." Quit }
 ;; Test character "a" case

 If $c(R)="a" {
   Use Write !!!,"A letter a has been read."
   Quit
 }
 ;; I added more code here to demonstrate "fall through" in original
 ;; when neither timeout nor "a" occurs
 Use 0
 Write !,"A character other than ""a"" was read"
 Quit

 ;; Since all 3 cases execute Use 0, this statement can be placed after Read and the other 3 copies deleted
 

It might be easier just to always make your BREAK commands conditional like (Robert Cemper suggests above).  Do it like:

    BREAK:$GET(^|"USER"|MyNameBreakAppEnable)

Use your own name for "MyName".

Then you do SET ^|"USER"|MyNameBreakAppEnable=1 to enable break points and
KILL ^|"USER"|MyNameBreakAppEnable to turn break points off.

Change the name of "App" to conditionalize break points for use in different applications.  And "USER" is a scratch namespace available on all the instances you plan to do debugging on.

The ObjectScript command
   ZBREAK %method^%CSP.Broker.1
or
   ZBREAK zmethod^%CSP.Broker.1
will allow you to set an ObjectScript debugging breakpoint at the entry of "%method" or "method" of that class.  The ObjectScript commands  BREAK "S" or BREAK "S+" would then allow you to single step through the statements in that method.  See documentation on ObjectScript debugging for more details.

Unfortunately, the ObjectScript routine %CSP.Broker.1 on my IRIS instance is "deployed" which means the %CSP.Broker.1.int routine source code is not available and I assume the %CSP.Broker.cls source code is also not available.  Without one of these source files you will be debugging and single stepping blind through the ObjectScript commands.  Some of InterSystems classes and routines are not "deployed" which means you can do Command-line Routine Debugging.  You can also modify those classes/routines after you change the IRISLIB database from readonly to read-write.

It is best that you take the advice above to contact support in the InterSystems WRC.