Here's a way without indirection:

Class MyPackage.MyClass
{

ClassMethod MyMethod(p1 = 1, p2 = 2, p3 = 3)
{
    Write p1,"-",p2,"-",p3,!
}

/// do ##class(MyPackage.MyClass).Test()
ClassMethod Test()
{
    Do ..FromJson({})
    Do ..FromJson({"p1":"first value", "p2":"second value"})
    Do ..FromJson({"p1":"first value", "p3":"third value"})
    Do ..FromJson({"p2":"second value"})
}

/// do ##class(MyPackage.MyClass).ArgPosMapping("MyPackage.MyClass", "MyMethod")
ClassMethod ArgPosMapping(class, method, Output map)
{
    kill map
    set formalspec = $$$comMemberKeyGet(class,$$$cCLASSmethod,method,$$$cMETHformalspecparsed)
    for i=1:1:$ll(formalspec) {
        set arg = $lg(formalspec, i)
        set map($lg(arg, 1)) =  i
    }
    
    set map = $ll(formalspec)
}

ClassMethod FromJson(json)
{
    Do ..ArgPosMapping($classname(), "MyMethod", .map)
    Set iterator = json.%GetIterator()
    Set position = 0
    While iterator.%GetNext(.key, .value) {
        Set position = position + 1
        Set arguments(map(key)) = value
    }
    
    Set arguments = map

    Do ..MyMethod(arguments...)
}

}

Tip.

Metadata does not change from one row to the other and accessing it takes time, so removing it from the row loop would improve the execution speed:

Class User.Test
{

Query TestQuery() As %SQLQuery
{
SELECT * FROM Sample.Person
}

/// do ##class(User.Test).Test()
ClassMethod Test()
{
    for method = "EveryRow", "Once" {
        set rSet = ..TestQueryFunc()
        
        set start = $zh
        do $classmethod(, method, rSet)
        set end = $zh
        
        write $$$FormatText("Method %1 took %2 sec.", method, end - start), !
    }
}

ClassMethod EveryRow(rSet As %SQL.ISelectResult)
{
    
    set tResults = []
    while rSet.%Next() {
        set tRow = {}
        set tMetadata = rSet.%GetMetadata()
        set tColumnCount = tMetadata.columns.Count()
        for x=1:1:tColumnCount {
            set tColumn = tMetadata.columns.GetAt(x)
            set tColumnName = tColumn.colName
            //do tRow.%Set(tColumnName, rSet.%GetData(x) )
            set $PROPERTY(tRow,tColumnName) = $PROPERTY(rSet,tColumnName)
        }
        do tResults.%Push(tRow)
    }
}

ClassMethod Once(rSet As %SQL.ISelectResult)
{
    set tResults = []
    set tColumns = ""
    
    set tMetadata = rSet.%GetMetadata()
    set tColumnCount = tMetadata.columns.Count()
    for x=1:1:tColumnCount {
        set tColumn = tMetadata.columns.GetAt(x)
        set tColumnName = tColumn.colName
        set tColumns = tColumns _ $lb(tColumnName)
    }

    while rSet.%Next() {
        set tRow = {}
        for x=1:1:tColumnCount {
            do tRow.%Set($lg(tColumns, x), rSet.%GetData(x) )
        }
        do tResults.%Push(tRow)
    }
}

}

Results for me:

>do ##class(User.Test).Test()
Method EveryRow took .017803 sec.
Method Once took .01076 sec.