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

Asked

Viewed 133 times

3

I’m using a webservice that gives me a list of an Object in JSON, and I’m trying to use Jsonfactory to extract the information from JSON 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 me a JSON with 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 dtEntry... Being of type totalcross.util.Date, you are giving the impression that Jsonfactory cannot convert. Has anyone had this problem? Have they found a solution to this?

1 answer

1


The class totalcross.json.JSONFactory interprets shallow data setters and deep objects with standard builder.

What would be a shallow datum? It’s data that doesn’t have data internally.

And deep object? Object I possess attributes internally.

Which blank data are recognised?

The primitives and their Wrappers are recognized. In addition to them, objects of the String are also considered shallow. The following 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, nor the object being produced. The 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 class Carro:

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

The following JSON would fail as Totalcross does not understand that the object Pessoa only has a single string attribute:

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

This would launch an exception with the following message:

Jsonobject[driver] is not a Jsonobject.

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

Circumventing the situation

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

  • DTO
  • Setter artificial
  • own JSON compiler

DTO

The strategy would be to build an equivalent DTO of the object and also a method that would transform the DTO into its 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, in which it does not require change in your business class.

Artificial setter

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

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

The first alternative requires a change in the serialization of the object, where it would no longer send the field as dtEntrada, but how dtEntradaStr.

Particularly, 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 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 Setter parameter setDtEntrada for 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. Much more effort, actually. The advantage of this one is that you can use a different DOM strategy. The package totalcross.json use the DOM strategy to interpret the Jsons.

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

An alternative strategy to the DOM is the alternative SAX. The SAX alternative allows you to interpret the dataset as a stream, nay needing mount the whole object.

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

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

To transform the HttpStream in a 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

Detection of the error character

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));

    [...]
}
  • Jefferson, in this example I passed, would it be a problem if I used the httpStream.readline() method instead of creating the byte array and reading based on the size of that array? Because since the registration amount coming from the webservice is variable, it may be that the size I put is not enough to read the line.

  • @Deivisoncardoso, the ideal is to read the entire stream =) If you think that the return of Webservice may be too great, I strongly recommend going after the SAX solution; otherwise (reasonably manageable return), it is possible to read all the content in memory. We made a wrapper class over Httpstream that can help you. I’ll update my response soon with considerations regarding the size of the data and how to deal with it

Browser other questions tagged

You are not signed in. Login or sign up in order to post.