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));
[...]
}