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:
- rename the JSON field from
dtEntrada
to another name, like dtEntradaStr
, and add the method setDtEntradaStr(String dtEntradaStr)
;
- 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));
[...]
}
Has more stack trace?
– Jefferson Quesado
Deivison, in editing my answer I placed an error detector
– Jefferson Quesado