Android Question [SOLVED]Custom Type To JSON String

Jorge M A

Well-Known Member
Licensed User
Longtime User
Hello Everybody,
Maybe it's a silly question, but I'm new in B4A.
Is there a simple way to generate a JSON string from a Custom Type?

<code>
Type VehiculoHeader (Id_Vehiculo As Int, _
Placa As String, _
Marca As String , _
Submarca As String, _
Modelo As String, _
Fecha_Alta As String )

Dim veh as VehiculoHeader
(fill veh)...

jsonString= ????

</code>

Thanks in advanced.
 

KMatle

Expert
Licensed User
Longtime User
Please use code tags (there's an icon)

1. Put the data to a map

B4X:
Dim VehiculoHeaderMap As Map
    VehiculoHeaderMap.Initialize
    VehiculoHeaderMap.put("Id_Vehiculo", VehiculoHeader.Id_Vehiculo)
    VehiculoHeaderMap.put("Placa", VehiculoHeader.Placa)

2. Convert it to a JSON string with

B4X:
Dim JSONGenerator As JSONGenerator
    JSONGenerator.Initialize(VehiculoHeaderMap)
  
    Dim JSONstring As String
    JSONstring = JSONGenerator.ToString
    Log(JSONstring)
 
Upvote 0

Jorge M A

Well-Known Member
Licensed User
Longtime User
Even though I have received fantastic responses, I would like to restate my original question. What I am trying to develop, is a generalized function that can receive a "custom type / Class" as an object, and in this way can go through each of its members to get the name of the field or property. In a similar way to the IntelliSense does in the IDE, or the Debugger in the Watch Window. I assume that "Reflection" is necessary, but I do not know how to use it.
Can you guide me to do this, if possible?
Thanks in advanced.
 

Attachments

  • List members.png
    List members.png
    26.2 KB · Views: 376
Upvote 0

Jorge M A

Well-Known Member
Licensed User
Longtime User
Thank you Erel. I'm trying to find some Documentation / Sample for B4XSerializator.
Any suggestion where to start?
 
Upvote 0

KMatle

Expert
Licensed User
Longtime User
What I am trying to develop, is a generalized function that can receive a "custom type / Class" as an object

Lists with Maps (converted to JSON) are exactly made for that:

- any language can handle lists and maps (B4x, Java, .net, VB, php, python and all the others
- you can convert it very easy to/from JSON
- you can easily transport it (as a Base64 string)
- add maps to a list an you're good
- you can loop through maps to get what you need (keys/values)

Of course you can create a class or a custom type and it's really "fancy" but from my experience you run into exact that problem we're talking about here. Don't get me wrong but the next thread will be "how can I use my class/custom type" in php or asp.net :D As I've written I exchange a lot of data between B4x and other systems.
 
Upvote 0

Jorge M A

Well-Known Member
Licensed User
Longtime User
@KMatle,

Maybe I find it difficult to explain myself. The point is that I have several Custom Types with many fields, which I have to exchange with third-party suppliers using JSON. These classes may change in the future due customer requirements.
The idea is to simplify the maintenance and reduce the size of the application, and avoid writing one by one each of the names of the fields (finally they are strings, and strings, and strings in the code) to convert them in their respective Maps in different places, then convert them to JSON.
Using Custom Types (as mini classes) is to facilitate the validation and data integrity by a development standard, not by being fancy.
My approach here is not related to using it in PHP or asp.net.
Either way, thank you very much for your advices.
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
which I have to exchange with third-party suppliers using JSON
Then you need to write helper subs which does converts a customtype to a map which then is used to build the json which you exchange.

When you get the data as json then you need to go the other way. get the json and parse it to a map. Use this map then to build your custom type.
Maybe you just can create the customtype while parsing the json...

The Point is that you need to have such helper methods if the exchangeformat is fix given. But it is surely possible to create such subs...
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
You now know what to do. Do it.
We can not give advices on how the subs should look like as we dont know you customtypes.

This may help to get started

B4X:
Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
    Type address (Name As String, Street As String, Town As String)
    Private jgen As JSONGenerator
    Private Button1 As Button
End Sub

B4X:
Sub Button1_Click
    Dim ActAddress As address
    ActAddress.Initialize
    ActAddress.Name = edtName.Text
    ActAddress.Street = edtStreet.Text
    ActAddress.Town = edtTown.Text
    Log(CT2JSON(ActAddress)) 
End Sub
Sub CT2JSON(adr As address) As String
    Dim m As Map
    m.Initialize
    m.Put("Name",adr.Name)
    m.Put("Street",adr.Street)
    m.Put("Town",adr.Town)
    jgen.Initialize(m)
    Return jgen.ToString
End Sub
 
Last edited:
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
and the other way...
B4X:
    Dim jsonstr As String = $"{"Name":"MyName","Street":"MyStreet","Town":"MyTown"}"$
    Dim parser As JSONParser
    parser.Initialize(jsonstr)
    Dim root As Map = parser.NextObject
    Dim adr As address
    adr.Initialize
    adr.Town = root.Get("Town")
    adr.Street = root.Get("Street")
    adr.Name = root.Get("Name")
    edtTown.Text = adr.Town
    edtStreet.Text = adr.Street
    edtName.Text = adr.Name
    Log("Address from json = "&adr)
 
Upvote 0

Roycefer

Well-Known Member
Licensed User
Longtime User
The following code is in B4J but it should be trivial to port it to B4A. It should get you started on the path to a generic ObjectToMap method that will return a JSON-able Map for any Object. The code requires the JavaObject and jReflection and Json libraries. It should not be too hard to extend this code to accommodate Arrays and Lists.

B4X:
'Non-UI application (console / server application)
#Region Project Attributes
    #CommandLineArgs:
    #MergeLibraries: True
#End Region

Sub Process_Globals
    Type TestType(t1 As String, t2 As Int, t3 As Boolean, t4 As Double)
End Sub

Sub AppStart (Args() As String)
    Dim t As TestType
    t.Initialize
    t.t1 = "Test"
    t.t2 = 5
    t.t3 = False
    t.t4 = cPI
    LogMap(ObjectToMap(t),False,"")
End Sub

'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub

Sub ObjectToMap(o As Object) As Map
    Dim primitives As List = Array As String("java.lang.String","java.lang.Integer","java.lang.Boolean","java.lang.Long", _
        "java.lang.Short","java.lang.Char","java.lang.Float","java.lang.Double")
    Dim m As Map
    m.Initialize
    m.Put("JavaType",GetType(o))
    If primitives.IndexOf(GetType(o))>-1 Then
        m.Put("value",o)
        Return m
    End If
    Dim r As Reflector
    r.Target = o
    Dim jo As JavaObject = Me
    Dim fields() As String = jo.RunMethod("GetFields", Array(o))
    For Each f As String In fields
        m.Put(f,ObjectToMap(r.GetField(f)))
    Next
    Return m
End Sub

'Handy little sub
Public Sub LogMap(m As Map, saveToFile As Boolean, fileName As String)
    Dim jg As JSONGenerator
    jg.Initialize(m)
    Log(jg.ToPrettyString(3))
    If saveToFile Then File.WriteString(File.DirApp, fileName, jg.ToPrettyString(3))
End Sub

#If JAVA
import java.lang.reflect.Field;
public static String[] GetFields(Object o)
{
    Class oc = o.getClass();
    Field[] fields = oc.getDeclaredFields();
    String[] res = new String[fields.length] ;
    for(int j=0;j<fields.length;j++)
    {
       res[j] = fields[j].getName();
    }
    return res;
}
#End If
 
Upvote 0

Roycefer

Well-Known Member
Licensed User
Longtime User
And if you need MapToObject, it should be straight-forward to write such a method inspired by the ObjectToMap method. You'll be using r.SetField instead of r.GetField.
 
Upvote 0

Jorge M A

Well-Known Member
Licensed User
Longtime User
Hi @Roycefer
I tried to adapt your code to B4A. However B4A doesn't include jReflection lib. I only found Reflection (without j) and add reference to it.

It seems Reflection doesn't have "GetFields" method (only in singular GetField)
ObjectToMap throw a java.lang.RuntimeException: Method: GetFields not found

i missing something?
 
Last edited:
Upvote 0

Roycefer

Well-Known Member
Licensed User
Longtime User
Here is the adapted B4A code:
B4X:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Type TestType(t1 As String, t2 As Int, t3 As Boolean, t4 As Double)
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.

End Sub

Sub Activity_Create(FirstTime As Boolean)
    'Do not forget to load the layout file created with the visual designer. For example:
    'Activity.LoadLayout("Layout1")
   
End Sub

Sub Activity_Resume
    Dim t As TestType
    t.Initialize
    t.t1 = "Test"
    t.t2 = 5
    t.t3 = False
    t.t4 = cPI
    Dim m As Map = ObjectToMap(t)
    LogMap(m,False,"")
End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub ObjectToMap(o As Object) As Map
    Dim primitives As List = Array As String("java.lang.String","java.lang.Integer","java.lang.Boolean","java.lang.Long", _
        "java.lang.Short","java.lang.Char","java.lang.Float","java.lang.Double")
    Dim m As Map
    m.Initialize
    m.Put("JavaType",GetType(o))
    If primitives.IndexOf(GetType(o))>-1 Then
        m.Put("value",o)
        Return m
    End If
    Dim r As Reflector
    r.Target = o
    Dim jo As JavaObject
    jo.InitializeContext
    Dim fields() As String = jo.RunMethod("GetFields", Array(o))
    For Each f As String In fields
        m.Put(f,ObjectToMap(r.GetField(f)))
    Next
    Return m
End Sub

'Handy little sub
Public Sub LogMap(m As Map, saveToFile As Boolean, fileName As String)
    Dim jg As JSONGenerator
    jg.Initialize(m)
    Log(jg.ToPrettyString(3))
    If saveToFile Then File.WriteString(File.DirRootExternal, fileName, jg.ToPrettyString(3))
End Sub

#If JAVA
import java.lang.reflect.Field;
public String[] GetFields(Object o)
{
    Class oc = o.getClass();
    Field[] fields = oc.getDeclaredFields();
    String[] res = new String[fields.length] ;
    for(int j=0;j<fields.length;j++)
    {
       res[j] = fields[j].getName();
    }
    return res;
}
#End If

For future reference, take note of the difference in how inline Java is called with JavaObject in B4A and B4J and the difference in the inline Java methods, themselves. This B4A code requires the Reflection, JavaObject and JSON libraries.
 
Upvote 0
Top