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

3

I'm using a webservice that returns me a list of an Object in JSON, and I'm trying to use JSONFactory to extract the JSON information to 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 Download 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 attributes of the class, and the date is in YYYY-MM-DD format ... When the JSONFactory.asList line is executed (...) it gives the following error: p>

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

Debugging the variable firstBytes contains the following value:

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

The problem is in dtEnter ... Because it is of type totalcross.util.Date, it is giving the impression that JSONFactory can not convert. Has anyone had this problem? Have you found a solution to this?

    
asked by anonymous 09.06.2017 / 20:28

1 answer

1

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

What would be a shallow die? They are data that does not have data internally.

And deep object? Object that has attributes internally.

What are the shallow data recognized?

Primitives and their wrappers are recognized. In addition, objects of type String are also considered shallow. Here 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 must have setters , which neither 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 class Carro :

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

The following JSON would fail because TotalCross does not 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 understanding of deep object (you do not have setters with attribute names), you can not use it in JSONFactory . But there are alternatives!

Skirting the situation

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

  • DTO
  • artificial setter
  • JSON compiler itself

DTO

The strategy would be to build an equivalent DTO 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 to be the least invasive, in that it does not require a change in your business class.

Artificial Setter

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

  • change the name of the JSON field from dtEntrada to another name, such as dtEntradaStr , and add method setDtEntradaStr(String dtEntradaStr) ;
  • change the setter setDtEntrada(Date dtEntrada) to receive as a parameter a String setDtEntrada(String dtEntradaStr) .
  • The first alternative requires a change in object serialization, where it would no longer send the field as dtEntrada , but as dtEntradaStr .

    In particular, I find the second alternative (change the setter to get another parameter) even more invasive.

    For the strategy of adding a new String setter, the class Descarga 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 parameter from setter setDtEntrada 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;
        }
    }
    

    JSON compiler itself

    This alternative involves more effort. Far more effort, actually. The advantage of this 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 move on to someone to interpret. Class JSONFactory works like this.

    An alternative strategy to the DOM is the SAX alternative. The SAX alternative allows you to interpret the data set as a stream , not needing to mount the entire object.

    A framework for dealing with JSON in an SAX strategy is JSON-Simple . We have made a significant port of JSON-Simple into TotalCross, classes continue with the same packages =)

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

    To convert HttpStream to Reader , do so:

    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, you may 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));
    
        [...]
    }
    
        
    09.06.2017 / 22:02