Here's an example of how we use the relationship between namespaces and databases.

My company sells ERP software for the millwork industry. Our customers sell building materials to contractors, distributors, etc. Some of our customers have multiple locations throughout the country. For those customers, we will usually set up a different namespace for each of those locations. Each of those namespaces is set up to have their own database for their data, but the all share the same database for routines because while they all have different data, like customers, orders, invoices, etc., the same set of code is running all of them.

@Jude Mukkadayil in one of your posts here, you mentioned this error:

[SQLCODE: <-415>:<Fatal error occurred within the SQL filer>]

[%msg: <Error occurring during UPDATE in table 'SQLUser.PA_Person': $ZE=<LIST>%SQLUpdate+40^User.PAPerson.1>]

According to this documentation, that could be a runtime error in some trigger code. In your User.PA_Person class's ObjectScript, you probably have a trigger defined that is causing issues for you. If so, can you post the code for the trigger(s) in that class?

I'm David Hockenbroch, and I'm based out of Memphis Tennessee. I'm a senior analyst here at WoodWare, so I get into all kinds of things. Lately that includes spending a lot of time considering how to get some really old pieces of software that were written in VB6 or using Zen pages into something that's supported now.

I'm a big food person, so Memphis is a good place for me! I also enjoy being outside or taking in a good story, whether that's a movie, book, TV show, or even a well-written videogame.

I will not be at READY 2025. I look at the session list every year, and it seems very healthcare-focused. I'm not in health care, so I always question whether it's worth the trip for me. We write business software. I do typically catch up on some of the keynotes online, though.

I'm not sure there's any getting around having to check to the HTTP status entirely, but you could log an exception without throwing it, then throw a more user-friendly exception to your catch block to be returned. It would also get logged, but that's probably okay as long as whoever is looking at the system logs knows that will happen.

//Log this one
do ##class(%Exception.StatusException).CreateFromStatus($$$ERROR($$$GeneralError,HTTPSRequest.HttpResponse.StatusCode_" "_HTTPSRequest.HttpResponse.Data.Read())).Log()
//Throw this one
if HTTPSRequest.HttpResponse.StatusCode = 502{
    throw ##class(%Exception.StatusException).CreateFromStatus($$$ERROR($$$GeneralError,"The service is currently unavailable. Please call tech support or try again later."))
}

Then some elseif blocks for whatever other HTTP statuses you want to handle, then a final "else" at the bottom to throw something generic to catch anything else.

That at least gets the custom handling out of the catch block so it isn't processed for every exception.

Here's where you're hurting yourself:

    if $$$ISERR(tSC) {
        set tSC = $$$ERROR($$$GeneralError, "Error in sending request to the server")
        quit tSC
    }

tSC is already a status. You are changing it to a different status and forcing it to say "Error in sending request to the server." Replace that whole block of code with:

quit:$$$ISERR(tSC) tSC

That will quit if tSC is an error and give you the actual status that occurred. Once you have that, it'll hopefully be easier to troubleshoot this.

I would use Dynamic Arrays and Dynamic Objects to make this simpler. Those two are your best friends when working with JSON.

ClassMethod GetAllPersons() As %Stream.Object
{
    d ..%SetContentType("application/json")
    Set rset = ##class(dc.Sample.Person).ExtentFunc()

    set stream=##class(%Stream.TmpCharacter).%New()
    set dynArray = [].%New()
    While rset.%Next() {
        do ##class(dc.Sample.Person).%OpenId(rset.ID).%JSONExportToString(.myPerson)
        Set dynObj = {}.%FromJSON(myPerson)
        do dynArray.%Push(dynObj)
    }
    d stream.Write(dynArray.%ToJSON())
    return stream
}

I know this has already been answered several ways, but let's not overlook embedded SQL as an option.

&sql(select JSON_OBJECT('Name':Name,'Title':Title,'Company':Company,'Phone':Phone, 'DOB':DOB) INTO :person WHERE ID = 1)
set personobj = {}.%FromJSON(person)

This can get a little unruly for tables with a lot of columns, but if you're wanting to pick out certain specific columns or customize the JSON field names, this approach gives you that flexibility.

I wrote my own class that does some fun, magical stuff. Here's the class:

/// This class can be used to make a status code do some basic handling on its own when it becomes an error status.
/// For example:<br /><br />
/// set status = ##class(DH.WatchedStatus).New("RW")<br />
/// set status.sc = (some method that returns a status here)<br /><br />
/// If the method returns an error status, it will immediately be written, and the status will change back to $$$OK.<br />
/// Note that if ..sc is not set back to an $$$OK status, either automatically or manually, error handling will not trigger again on the next error.
Class DH.WatchedStatus Extends %RegisteredObject
{

/// This is the status code to be watched.
Property sc As %Status [ InitialExpression = 1, SqlComputeCode = {set {*} = ##class(DH.WatchedStatus).Reset({sc},{resetSelf})}, SqlComputed, SqlComputeOnChange = isErr ];

/// Used to track if the status code is an error. This is necessary for some shenanigans with SQLComputeCode between this flag and the status code.
Property isErr As %Boolean [ InitialExpression = 0, SqlComputeCode = {set {*} = ##class(DH.WatchedStatus).ErrCheck({sc},{writeSelf},{logSelf},{throwSelf})}, SqlComputed, SqlComputeOnChange = sc ];

/// If true, this will will throw ..sc as soon as it becomes an error.
Property throwSelf As %Boolean [ InitialExpression = 0 ];

/// If true, this will log ..sc as an Exception as soon as it becomes an error.
Property logSelf As %Boolean [ InitialExpression = 0 ];

/// If true, this will write the error text of ..sc as soon as it becomes an error.
Property writeSelf As %Boolean [ InitialExpression = 0 ];

/// If true, after other error handling, ..sc will be reset to $$$OK.
/// Note that if this is false, you will need to reset the status yourself for the automatic handling to trigger again.
Property resetSelf As %Boolean [ InitialExpression = 0 ];

/// Handles status according to flags set, then sets isErr.
ClassMethod ErrCheck(sc, writeSelf, logSelf, throwSelf) As %Boolean [ Internal ]
{
	if $$$ISERR(sc){
		if writeSelf{
			write $SYSTEM.Status.GetErrorText(sc)
		}
		if logSelf = 1{
			do ##class(%Exception.StatusException).CreateFromStatus(sc).Log()
		}
		if throwSelf = 1{
			$$$ThrowStatus(sc)
		}
		quit 1
	}
	else{
		quit 0
	}
}

/// If resetSelf is true, resets the status code after error handling occurs.
ClassMethod Reset(sc, resetSelf) As %Status [ Internal ]
{
	return:resetSelf $$$OK
	return sc
}

/// flags is a string which determines status behavior when an error occurs
/// T = throw the status
/// L = log the status as an exception
/// W = write the status error text
/// R = reset status after error handling; if set, isErr goes back to 0 and sc goes back to 1
ClassMethod New(flags As %String) As DH.WatchedStatus
{
	set status = ##class(DH.WatchedStatus).%New()
	set flags = $ZCVT(flags,"U")
	set:(flags [ "T") status.throwSelf = 1
	set:(flags [ "L") status.logSelf = 1
	set:(flags [ "W") status.writeSelf = 1
	set:(flags [ "R") status.resetSelf = 1
	return status
}

}

Here's how I can use it:

set mystatus = ##class(WoodWare.Status).New("WR")
set mystatus.sc = (something that returns a status)

Having done that, since I used the write and reset flags in the constructor, it will write out the error text and then reset the status object back to $$$OK. I can use this to save myself from constantly having to check whether the status is an error and log or throw it in other code outside the terminal too. It essentially watches itself.

Update: this is now available on IPM.

install watchedstatus

If it's a simple get request, you can test from the terminal without setting up a %Net.HttpRequest. I usually do it like this, in a terminal (for more complicated ones with a request body, you have to initialize %request to a %CSP.Request and set it up too):

set %response = ##class(%CSP.Response).%New()
do ##class(Your.Class).YourMethod(yourarguments)
zw %response