Android Question [B4X] Cast Map to Custom Type

Mike1970

Well-Known Member
Licensed User
Longtime User
Hi everyone I was wondering if it possibile to cast a Map object to a Custom type object that has the same fields.
At the moment I'm doing with a function that manually get all the fields from the map.

Thanks in advance
 

Mahares

Expert
Licensed User
Longtime User
cast a Map object to a Custom type object that has the same fields.
Why not something like this . Maybe you should provide an example if I am not addressing your question correctly:
B4X:
Sub Class_Globals
    Private Root As B4XView
    Private xui As XUI
    Type maptocustom(mymap As Map)    
End Sub

Public Sub Initialize
'    B4XPages.GetManager.LogEvents = True
End Sub

'This event will be called once, before the page becomes visible.
Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Dim mp As Map
    mp.Initialize
    mp=CreateMap("name":"Mike", "city":"Roma", "country":"Italia")
    Dim mc As maptocustom = Createmaptocustom (mp)     
    Dim x As Map = mc.mymap
    For Each s As String In x.Keys
        Log($"${s}        ${x.Get(s)}"$)
    Next
End Sub

Public Sub Createmaptocustom (mymap As Map) As maptocustom
    Dim t1 As maptocustom
    t1.Initialize
    t1.mymap = mymap
    Return t1
End Sub
 
Upvote 0

Mike1970

Well-Known Member
Licensed User
Longtime User
Hi, thanks for your interest, actually what I was looking for is something similar to Java and Python

if I've a map like
B4X:
Dim mp As Map
mp.Initialize
mp=CreateMap("Name":"Mike", "City":"Roma", "Country":"Italia")

I need something that cast it directly in a custom type done like this
B4X:
Type User(Name as String, City As String, Country As String)

I think must be an IDE function, if exists.
like

B4X:
dim u as User
u = mp
or
B4X:
dim u as User
u = mp.As(User)
 
Upvote 0

William Lancee

Well-Known Member
Licensed User
Longtime User
How about this?

B4X:
Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Root.LoadLayout("MainPage")

    'In Class_Globals:    Type MyType(Name As String, City As String, Country As String)
    ' hover over 'MyType to generate CreateMyType, replace arguments as shown below
   
    Dim Person1 As MyType = CreateMyType(CreateMap("Name":"Mike", "City":"Roma", "Country":"Italia"))
    Log(Person1.Name & TAB & Person1.City & TAB & Person1.Country)
End Sub

Public Sub CreateMyType(mp As Map) As MyType
    Dim t1 As MyType
    t1.Initialize
    t1.Name = mp.Get("Name")
    t1.City = mp.Get("City")
    t1.Country = mp.Get("Country")
    Return t1
End Sub
 
Upvote 0

Mike1970

Well-Known Member
Licensed User
Longtime User
Yes this is what I've done , I was wondering if there was something "elite" to do the casting, but the answer seems to be no unfortunately
 
Upvote 0

William Lancee

Well-Known Member
Licensed User
Longtime User
Just for fun, you can automate this procedure by running this code.
It generates the type statement and the initialization sub for any map with primitive elements.
It puts the code on the Log...

Edit: Some improvements to help copying from Log to app

B4X:
'This sub creates all the code needed and places it on the log
'To use, just once then comment:
'    GenerateCustomType("MyType", CreateMap("Name":"Mike", "City":"Roma", "Country":"Italia", "Amount": 1000000))
Private Sub GenerateCustomType(typeName As String, mp As Map)
    Dim sb As StringBuilder: sb.Initialize
    sb.Append("'Place this line in Globals").Append(CRLF)
    sb.Append("'").Append(TAB).Append("Type ").Append(typeName).Append("(")
    For Each kw As String In mp.Keys
        Dim obj As Object = mp.Get(kw)
        Dim typ As String = GetType(obj)
        typ = typ.SubString(typ.LastIndexOf(".") + 1).trim
        If typ = "Integer" Then typ = "Int"
        sb.Append(kw).Append(" As ").Append(typ).Append(", ")
    Next
    If sb.Length > 1 Then sb.Remove(sb.Length - 2, sb.Length)
    sb.Append(")").Append(CRLF)
    sb.Append("'").Append(CRLF)
    sb.Append("'").Append("Public Sub Create").Append(typeName).Append("(mp As Map) As ").Append(typeName).Append(CRLF)
    sb.Append("'").Append(TAB).Append("Dim t1 As MyType").Append(CRLF)
    sb.Append("'").Append(TAB).Append("t1.Initialize").Append(CRLF)
    For Each kw As String In mp.Keys
        sb.Append("'").Append(TAB).Append("t1.").Append(kw).Append(" = ").Append("mp.Get(").Append(QUOTE).Append(kw).Append(QUOTE).Append(")").Append(CRLF)
    Next
    sb.Append("'").Append(TAB).Append("Return t1").Append(CRLF)
    sb.Append("'").Append("End Sub")
    
    Log(sb.ToString)
End Sub

'On log:

'Place this line in Globals
'    Type MyType(Name As String, City As String, Country As String, Amount As Int)
'
'Public Sub CreateMyType(mp As Map) As MyType
'    Dim t1 As MyType
'    t1.Initialize
'    t1.Name = mp.Get("Name")
'    t1.City = mp.Get("City")
'    t1.Country = mp.Get("Country")
'    t1.Amount = mp.Get("Amount")
'    Return t1
'End Sub
 
Last edited:
Upvote 0

William Lancee

Well-Known Member
Licensed User
Longtime User
It is possible to do it the other way around, and it is more intuitive.
Start with the type statement and generate the MyTypeFromMap sub with the parser below.

I've added more bells and whistles and made it more robust.
The map can have more or less fields defined then the type.

I can see it working as follows. You have a map derived from a JSON file. There may be one or more custom types in there.
Run the GenerateCustomType for each type once in development (remove or comment when satisfied).
Then create and use the instances of each type. The code below is about 80 lines. The code generator itself is a dozen lines.

B4X:
Sub Class_Globals
    Type MyType(Name As String, City As String, Country As String, _
    Amount As Int, Grade As Float, Phones As List, TestMap As Map, TestArray() As Int)
End Sub

Private Sub B4XPage_Created (Root1 As B4XView)
'Run just once to put required Sub on the log, argument is direct copy of Type statement
    GenerateCreateType($"
    Type MyType(Name As String, City As String, Country As String, _
    Amount As Int, Grade As Float, Phones As List, TestMap As Map, TestArray() As Int)
"$)
    
    Dim Person1 As MyType = MyTypeFromMap(CreateMap("Name":"Mike", "City":"Roma", "Country":"Italia", _
    "Amount": 1000000, "Grade": 2.5, "Phones": CreateList(Array(1111111111, 2222222222, 3333333333)), _
    "TestMap": CreateMap("Member": "William"), "TestArray": Array As Int(1,2,3)))

    Log(Person1.Name & TAB & Person1.City & TAB & Person1.Country & TAB & Person1.Amount & _
    TAB & Person1.Grade & TAB & Person1.Phones.Size & TAB & Person1.testMap.Get("Member") & TAB & Person1.TestArray(2))
End Sub

Private Sub GenerateCreateType(s As String)
    s = s.Replace(CRLF, " ").Replace(" _ ", " ")        'to combine multiline declaration
    Dim v() As String = TwoParts("(", s.trim)
    Dim tname As String = v(0).substring(v(0).IndexOf(" ")+ 1)
    Dim sb As StringBuilder: sb.Initialize
    Dim args() As String = Regex.Split("\,", v(1).substring2(0, v(1).Length - 1))
    For i = 0 To args.Length - 1
        v = IIf(args(i).IndexOf("(") > -1, TwoParts("(", args(i).trim), TwoParts(" ", args(i).trim))
        sb.Append($"'${TAB}If mp.ContainsKey("${v(0)}") Then t1.${v(0)} = mp.Get("${v(0)}")${CRLF}"$)
    Next
    Dim s As String = $"
'Public Sub ${tname}FromMap(mp As Map) As ${tname}
'    Dim t1 As ${tname}
'    t1.Initialize
${sb.ToString}
'    Return t1
'End Sub
    "$
    Log(s)
End Sub

Private Sub TwoParts(delim As String, s As String) As String()
    s = s.trim
    Dim result(2) As String
    result(0) = s
    Dim k As Int = s.IndexOf(delim)
    If k > - 1 Then
        result(0) = s.SubString2(0, k).trim
        result(1) = s.SubString(k + delim.length).trim
    End If
    Return result
End Sub

Private Sub CreateList(ar() As Object) As List
    Dim a As List
    a.Initialize
    For Each obj As Object In ar
        a.Add(obj)
    Next
    Return a
End Sub

'Copied from log, generated by: GenerateCreateType
Public Sub MyTypeFromMap(mp As Map) As MyType
    Dim t1 As MyType
    t1.Initialize
    If mp.ContainsKey("Name") Then t1.Name = mp.Get("Name")
    If mp.ContainsKey("City") Then t1.City = mp.Get("City")
    If mp.ContainsKey("Country") Then t1.Country = mp.Get("Country")
    If mp.ContainsKey("Amount") Then t1.Amount = mp.Get("Amount")
    If mp.ContainsKey("Grade") Then t1.Grade = mp.Get("Grade")
    If mp.ContainsKey("Phones") Then t1.Phones = mp.Get("Phones")
    If mp.ContainsKey("TestMap") Then t1.TestMap = mp.Get("TestMap")
    If mp.ContainsKey("TestArray") Then t1.TestArray = mp.Get("TestArray")
    Return t1
End Sub

'On Log

'Public Sub MyTypeFromMap(mp As Map) As MyType
'    Dim t1 As MyType
'    t1.Initialize
'    If mp.ContainsKey("Name") Then t1.Name = mp.Get("Name")
'    If mp.ContainsKey("City") Then t1.City = mp.Get("City")
'    If mp.ContainsKey("Country") Then t1.Country = mp.Get("Country")
'    If mp.ContainsKey("Amount") Then t1.Amount = mp.Get("Amount")
'    If mp.ContainsKey("Grade") Then t1.Grade = mp.Get("Grade")
'    If mp.ContainsKey("Phones") Then t1.Phones = mp.Get("Phones")
'    If mp.ContainsKey("TestMap") Then t1.TestMap = mp.Get("TestMap")
'    If mp.ContainsKey("TestArray") Then t1.TestArray = mp.Get("TestArray")
'    Return t1
'End Sub
 
Upvote 0

Mike1970

Well-Known Member
Licensed User
Longtime User
it is not what was I was looking for, but it is very cool the idea you had
Thanks for sharing
 
Upvote 0
Cookies are required to use this site. You must accept them to continue using the site. Learn more…