Reports are a big, separate issue from modern web applications technologies.  Unless you're writing a reports web application.

This can be slow/bad in applications with large data?

Sure. To avoid that make your REST service logically asynchronous (see DeepSee REST API for MDX execution):

  • User sends a query
  • Server immediately returns query key
  • Server executes query into another (jobbed) process
  • Some time later user sends query key
  • Server returns current query status (in progress, done)
  • If query is done send user the data

Also, if you're using pure SQL, scrollable resultset can be used in a similar fashion.

  It's better/recommended to use ZenReports even with ZenMojo applications?

It's just another approach to reports.

  Or Zen Mojo, like any other applications based on HTML, JS and PHP can support this with any problem?

Reports can be done using ZEN Mojo, sure.

The preparation of the JSON is very fast because the use of %DynamicObject and %DynamicArray; ZenReport can be slower than it?

Time to render JSON is negligible compared to time time you need to generate a report. ZenReport calls other software to render report files, so it's slower, sure, but still I think the main timesink here is execution and not rendering.

To sum up, your requirements determine the necessarily approach.

The benefits of using list of %String  over just %String are:

  • You can easily index individual values
  • You can project and access values as a separate table

I actually had a use case for exactly this data. Requirements were:

  • There are alerts and users
  • Alerts are sent once an hour
  • Each alert is sent to multiple users
  • Each user should receive only one digest email with all relevant alerts

List of %String was extremely useful in this case. First I wrote a temp table:

/// Store alerts (for current process and runtime-only)
Class Util.Alert Extends %Persistent
{

/// Alert topic
Property topic As %String(MAXLEN = 1000) [ Required ];

/// Alert text
Property text As %String(MAXLEN = "");

/// Recipients
Property emails As list Of %String(SQLPROJECTION = "table/column", STORAGEDEFAULT = "array");

/// Index
Index emailsIndex On emails(ELEMENTS);

/// Add one Alert
/// w ##class(Util.Alert).add("Error", $lb("1@1.com","2@2.com"), "text")
ClassMethod add(topic As %String, text As %String, emails As %List) As %Status
{
    set obj = ..%New()
    set obj.topic = topic
    set obj.text = text
    
    if $listvalid(emails) {
        for i=1:1:$ll(emails){
            do obj.emails.Insert($lg(emails,i))
        }
    }
    return obj.%Save()
}

}

Next I wrote a business service which searched for alerts and added them to this table (not relevant for this discussion). And then once all alerts for an hour are in the Util.Alert table, sendEmails method can easily send alerts in digest mode:

/// Generate emails from alerts and send them
ClassMethod sendEmails()
{
    &sql(DECLARE C1 CURSOR FOR
         SELECT DISTINCT emails  
         INTO :email
         FROM Util.Alert
    )
 
    &sql(OPEN C1)
    &sql(FETCH C1)

    While (SQLCODE = 0) {
        set text = ..generateEmailText(email)
        do ..SendEmail(email, text)
        &sql(FETCH C1)
    }
    &sql(CLOSE C1)
}

/// Create email with all alerts for user
ClassMethod generateEmailText(email As %String) As %String
{
    set emailText = ""
    &sql(DECLARE C2 CURSOR FOR
         SELECT topic, text  
         INTO :topic, :text
         FROM Util.Alert
         WHERE FOR SOME %ELEMENT(emails) (%VALUE=:email)
    )

    &sql(OPEN C2)
    &sql(FETCH C2)

    While (SQLCODE = 0) {        
        set emailText = emailText _  topic _ ": " _ text _ $$$NL
         &sql(FETCH C2)
    }
    &sql(CLOSE C2)
    
    return emailText
}

Without list of %String SQL here would be far harder to write or slower.