If data is too big for a string then result would also be too big for a string.

 I think something like this would work:

  1. Create result stream
  2. Find positions of the beginning and the end of the first placeholder in the template
  3. Write template from the start to the beginning of the first placeholder
  4. Copy data stream into  result stream
  5. Repeat 2-4 till you have no more placeholders (if there's more than one placeholder per template)
  6. Send result stream out via a soap webservice

It can be done as one method with this signature ( so it would be possible to pass any number of data streams for one template):

ClassMethod FillTemplate(Template As %String, Data... As %Stream.GlobalCharacter) As %Stream.GlobalCharacter {}

The  easy way to display list object:

for i=1:1:user.Roles.Count() w user.Roles.GetAt(i),!

You can also serialize list object to $list and display it:

zw user.Roles.Serialize()

Also note that lists of datatypes defined as class properties are of %Collection.ListOfDT class (which is somewhat similar but not identical to %ListOfDataTypes class).

You can also get all information on %ListOfDataTypes objects with zw command:

s b=##class(%ListOfDataTypes).%New()
do b.Insert(1)
do b.Insert(2)
do b.Insert(3)
zw b

Great article, I find this feature very useful too, here's my %ZLANGC00:

 ; %ZLANGC00
 ; custom commands for ObjectScript
 ; http://localhost:57772/csp/docbook/DocBook.UI.Page.cls?KEY=GSTU_customize
  Quit    

/// Execute Query and display the results
/// Call like this:
/// zsql "SELECT TOP 10 Name FROM Sample.Person"
ZSQL(Query)
    #Dim ResultSet As %SQL.StatementResult
    Set ResultSet = ##class(%SQL.Statement).%ExecDirect(, Query)
    Do ResultSet.%Display()
    Quit

/// Move user to a namespace of his choice
/// Call like this:
/// zm "s"
ZM(Namespace)
    Do MoveToNamespace^%ZSTART(Namespace)
    Quit
    
/// Move to Samples namespace and set a as a Samples.Person object
/// Set b as %ZEN.proxyObject
/// Set c as %Object
ZO(move = 1) Public
    ZN:move "SAMPLES"
    If ##class(%Dictionary.CompiledClass).%ExistsId("Sample.Person") {
        Set p = ##class(Sample.Person).%OpenId(1)
    }
    Set a = ##class(%ZEN.proxyObject).%New()
    Set b = {}
    Quit

and related %ZSTART:

%ZSTART() {
    Quit    
}

/// This entry point is invoked on user login
/// offering user to choose a namespace
LOGIN() Public {
    Set Timeout = 3
    Write "Namespace <" _ $Namespace _ ">: "  
    Read Namespace:Timeout // Get value of a Namespace variable
    Quit:Namespace=""
    Do MoveToNamespace(Namespace)
}    

/// Does actual moving to a chosen namespace
/// This is an entry point, for cases where
/// Namespace value is already aquired
MoveToNamespace(Namespace = "") Public {
    Set Timeout = 3
    #Dim List As %ListOfDataTypes
    Set List = $$GetNamespaceList(Namespace)
    Set Count = List.Count()
    
    If Count = 1 {
        Set Choice = 1
    } ElseIf Count > 1 {
        Do DisplayList(List)

        // If there is less then 10 results, then we need only 1 digit
        // Otherwise we need 2 digits
        // It is assumed that no more then 99 results would be returned
        Read "Select number <1>: ", Choice#$Select(Count < 10:1, 1:2):Timeout

        // If the user entered nothing or not a valid number
        // we select first namespace in a list to go to
        Set:((Choice = "") || ('$IsValidNum(Choice, 0, 1, Count))) Choice = 1
    } Else {
        // No namespaces found
        Quit
    }
    
    Zn List.GetAt(Choice)
}

/// Get all availible namespaces that satisfy
/// "Name %STARTSWITH Namespace" condition
/// as %ListOfDataTypes
GetNamespaceList(Namespace = "") {
    New $Namespace
    Set $Namespace = "%SYS"

    #Dim List As %ListOfDataTypes
    #Dim ResultSet As %SQL.StatementResult
    Set List = ##class(%ListOfDataTypes).%New()

    Set UserCondition = "%UPPER(Name) %STARTSWITH %UPPER(?)"  // Or [ if you wish
    Set Condition="(" _ UserCondition _ ") AND (SectionHeader='Namespaces') AND (%UPPER(Name)!='%ALL')"
    Set SQL = "SELECT Name FROM Config.Namespaces WHERE " _ Condition

    Set ResultSet = ##class(%SQL.Statement).%ExecDirect(, SQL, Namespace)
    While ResultSet.%Next() {
        Do List.Insert(ResultSet.%Get("Name"))
    }

    Quit List
}

/// Display %ListOfDataTypes in a format:
/// 1    item
/// 2    item
/// ...
DisplayList(List) {
    #Dim List As %ListOfDataTypes
    Write !
    For i = 1:1:List.Count() {
        Write i, $C(9), List.GetAt(i), !
    }
}

Github repo.

Both BuildValueArray and LogicalToDisplay work with serialized form of %ListOfDataTypes object - $list string:

do ##class(%ListOfDataTypes).BuildValueArray($lb("a","b","c"), .out)
zw out

Outputs:

out(1)="a"
out(2)="b"
out(3)="c"

To sum up:

  • %ListOfDataTypes - object
  • serialized %ListOfDataTypes  - $list
  • $list - a string (with special properties, but not an object)
  • %List - $list

Yes, I'm aware of that.  There is not that much records (hundreds of thousands tops). Still, decided to do a comparison:

ClassMethod Time(count = 100000000)
{
    Set str = "111111111111111111111111111111111111111111111111111111111"
    Set time1 = $P($h, ",", 2)
    For i=1:1:count {
        s a = $zcrc(str, 7)
    }
    Set time2 = $P($h, ",", 2)
    For i=1:1:count {
        s a = $System.Encryption.MD5Hash(str)
    }
    Set time3 = $P($h, ",", 2)
    For i=1:1:count {
        s a = $System.Encryption.SHA1Hash(str)
    }
    Set time4 = $P($h, ",", 2)
    For i=1:1:count {
        s a = $System.Encryption.SHAHash(256, str)
    }
    Set time5 = $P($h, ",", 2)
    Write !,"CRC: ",time2-time1,!,"MD5: ",time3-time2,!,"SHA1: ",time4-time3,!,"SHA2: ",time5-time4
}

It outputs the following results:

CRC: 14
MD5: 72
SHA1: 119
SHA2: 140

Yes.  Use resources instead. Read the docs. $ZPARENT value is also a "resource" that's getting created with the process, so children processes can communicate events to a parent process easily. As processes you want to pass messages between are not parent and child,  you need to create and use explicitly named resources.