How to extract an attribute of type totalcross.util.Date from a JSON

Question:

I'm using a webservice that returns a list of an Object in JSON, and I'm trying to use JSONFactory to extract the JSON information into a List.

HttpStream httpStream = new HttpStream(new URI(VarGlobais.url + "/descarga/listDescarga"), options);

    if (httpStream.isOk()) {

        byte[] BUFF = new byte[2048];
        int read = httpStream.readBytes(BUFF, 0, BUFF.length);
        String firstBytes = new String(BUFF, 0, read);

        List<Descarga> listDescarga = JSONFactory.asList(firstBytes, Descarga.class);

        [...]
    }

The Discharge class has the following attributes

import totalcross.util.Date;

[...]

private Integer seqDescarga;
private Integer cdEmpresa;
private Integer cdFilial;
private String placa;
private String siglaUfPlaca;
private Date dtEntrada;
private String hrEntrada;

[GETS / SETS]

The webservice returns a JSON with the class attributes, and the date is in YYYY-MM-DD format… When the JSONFactory.asList(…) line is executed, it gives the following error:

GRAVE: null
totalcross.json.JSONException: A JSONObject text must begin with '{' at 1 [character 1 line 0]

Debugging the firstBytes variable contains the following value:

{"seqDescarga":5456,"cdEmpresa":1,"cdFilial":28,"placa":"EPE3821","siglaUfPlaca":"SP","dtEntrada":"2017-06-09","hrEntrada":"170132"}

The problem is in dtEntrada… Because it is of the totalcross.util.Date type, it is giving the impression that JSONFactory cannot convert. Has anyone had this problem? Did you find any solution for this?

Answer:

The totalcross.json.JSONFactory class interprets shallow data setters and deep objects with default constructor.

What would be a shallow dice? It is data that does not have data internally.

And deep object? Object that I have attributes internally.

What is the recognized shallow data?

Primitives and their wrappers are recognized. Besides them, objects of type String are also considered shallow. Below is the list of types:

  • int
  • boolean
  • long
  • double
  • java.lang.Integer
  • java.lang.Boolean
  • java.lang.Long
  • java.lang.Double
  • java.lang.String

dealing with deep objects

For TotalCross to be able to properly use a deep object, it needs to have setters , just like the object being produced. JSONFactory will interpret that deep objects are mapped as JSON objects as well. For example, we could have the following structure:

class Pessoa {
    private String nome;
    
    // getter/setter
}

class Carro {
    private String placa;
    private String modelo;

    private Pessoa motorista;

    //getters/setters
}

TotalCross would be able to interpret the following JSON sent as being of the Carro class:

{
    'placa' : 'ABC1234',
    'model' : 'fusca',
    'motorista': {
         'nome' : 'Jefferson'
    }
}

The following JSON would fail, as TotalCross doesn't understand that the Pessoa object only has a single string attribute:

{
    'placa' : 'ABC1234',
    'model' : 'fusca',
    'motorista': 'Jefferson'
}

This would throw an exception with the following message:

JSONObject[driver] is not a JSONObject.

As the totalcross.util.Date class does not fit the deep object understanding (it has no setters with the attribute names), it is not possible to use it in JSONFactory . But there are alternatives!

bypassing the situation

There are some workarounds to work around these issues. The ones I can easily imagine now are:

  • DTO
  • artificial setter
  • own JSON compiler

DTO

The strategy would be to build a DTO equivalent of the object and also a method that would transform the DTO into your business object:

// classe do DTO apenas com dados rasos
class DescargaDTO {
    private Integer seqDescarga;
    private Integer cdEmpresa;
    private Integer cdFilial;
    private String placa;
    private String siglaUfPlaca;
    private String dtEntrada;
    private String hrEntrada;

    // getters/setters
}

// método que transforma o DTO no objeto desejado
public static Descarga dto2Descarga(DescargaDTO dto) {
    if (dto == null) {
        return null;
    }
    Descarga descarga = new Descarga();

    descarga.setSeqDescarga(dto.getSeqDescarga());
    descarga.setPlaca(dto.getPlaca());
        // ... demais campos rasos ...

    try {
        descarga.setDtEntrada(new Date(dto.getDtEntrega(), totalcross.sys.Settings.DATE_YMD));
    } catch (InvalidDateException e) {
        // tratar formato de data inválida do jeito desejado; pode até ser lançando a exceção para o chamador deste método tratar
    }

    return descarga;
}

This DTO scheme I consider the least invasive, as it doesn't require changing your business class.

artificial setter

This strategy requires changing the business class, so it is more invasive than the previous one. The intention here would be to have a setCampoJson(String campoJson) for a campoJson . We can implement this in two ways:

  1. rename the JSON field from dtEntrada to another name, such as dtEntradaStr , and add the method setDtEntradaStr(String dtEntradaStr) ;
  2. change the setDtEntrada(Date dtEntrada) to receive as parameter a String setDtEntrada(String dtEntradaStr) .

The first alternative requires a change to the object's serialization, where it would no longer send the field as dtEntrada , but as dtEntradaStr .

Personally, I find the second alternative (changing the setter to receive another parameter) even more invasive.

For the strategy of adding a new String setter, the Descarga class would look like this:

class Descarga {
    private Integer seqDescarga;
    private Integer cdEmpresa;
    private Integer cdFilial;
    private String placa;
    private String siglaUfPlaca;
    private Date dtEntrada;
    private String hrEntrada;

    // getters/setters reais

    public void setDtEntradaStr(String dtEntradaStr) {
        setDtEntrada(new Date(dtEntradaStr, totalcross.sys.Settings.DATE_YMD)); // TODO tratar a exceção possivelmente lançada pelo construtor, seja com try-catch ou lançando a exceção para o chamador
    }
}

In the option to change the setDtEntrada setter parameter to String:

class Descarga {
    private Integer seqDescarga;
    private Integer cdEmpresa;
    private Integer cdFilial;
    private String placa;
    private String siglaUfPlaca;
    private Date dtEntrada;
    private String hrEntrada;

    // getters/setters para todos os atributos EXCETO dtEntrada

    public void setDtEntrada(String dtEntradaStr) {
        dtEntrada = new Date(dtEntradaStr, totalcross.sys.Settings.DATE_YMD); // TODO tratar a exceção possivelmente lançada pelo construtor, seja com try-catch ou lançando a exceção para o chamador
    }

    public Date getDtEntrada() {
        return dtEntrada;
    }
}

Own JSON Compiler

This alternative involves more effort. A lot more effort, actually. The advantage of this one is that you can use a different DOM strategy. The totalcross.json package uses the DOM strategy to interpret the JSONs.

The DOM strategy is to assemble the entire information tree and then pass it on to someone else to interpret. The JSONFactory class works like this.

An alternative strategy to the DOM is the SAX alternative. The SAX alternative allows interpreting the dataset as a stream , not needing to assemble the entire object.

One framework for handling JSON in a SAX strategy is JSON-Simple . We made a significant port of JSON-Simple into TotalCross, the classes still have the same packages =)

To compile SAX mode, implement the ContentHandler interface and call it in the JSONParser.parse(Reader in, ContentHandler contentHandler) .

To turn HttpStream into a Reader , do this:

public void exemploParseJson(HttpStream stream) java.io.IOException, org.json.simple.parser.ParseException {
    JSONParser parser = new JSONParser();
    parser.parse(new InputStreamReader(stream.asInputStream(), getMyContentHandler());
}

We have an example of ContentHandler here .

Update

Error Character Detection

Hypothetically, it might be reading more bytes than just JSON. For debugging, we can do the following test:

static class JsonTokenerTest extends JSONTokener {
    
    public JsonTokenerTest(String s) {
        super(s);
    }

    @Override
    public JSONException syntaxError(String message) {
        this.back();
        return new JSONException(message + " around this char: '" + this.next() + "' " + this.toString());
    }
}

HttpStream httpStream = new HttpStream(new URI(VarGlobais.url + "/descarga/listDescarga"), options);

if (httpStream.isOk()) {

    byte[] BUFF = new byte[2048];
    int read = httpStream.readBytes(BUFF, 0, BUFF.length);
    String firstBytes = new String(BUFF, 0, read);

    JSONObject teste = new JSONObject(new JsonTokenerTest(firstBytes));

    [...]
}
Scroll to Top
AllEscort