CodenameOne: PropertyIndex.toJSON() not generating correct JSON for Lists of Objects - codenameone

I have a mobile app where a user fills out a form, say an Event, and when they save I want to submit the data as JSON to the server. CN1 has the feature to generate JSON easily using PropertyBusinessObject so my Event is defined as follows:
public class Event implements PropertyBusinessObject {
public final Property<Long, Event> eventId = new Property<>("eventId");
public final Property<EventLocation, Event> eventLocation = new Property<>("eventLocation", EventLocation.class);
public final Property<List<EventItinerary>, Event> eventItineraryList = new Property<>("eventItineraryList", XXX);
private final PropertyIndex idx...
}
Both EventLocation and EventItinerary implement PropertyBusinessObject and I've found that when I generate the JSON for Event, EventLocation generates fine but not EventItinerary. When I try and use EventItinerary.class in the "XXX" section above, I get the following error...
Exception: java.lang.ClassCastException - class java.util.ArrayList cannot be cast to class com.codename1.properties.PropertyBusinessObject
which occurs at line 484 of com.codename1.properties.PropertyIndex.toMapRepresentationImpl()
When I use List.class for "XXX" or nothing i.e. new Property<>("eventItineraryList"); then it posts to the server but the JSON contains the name of the class and its memory address i.e.
{
"eventId": 3425567,
"eventLocation" : {
...
},
"eventItineraryList": [
"com.myapp.event.EventItinerary#cdc543c",
"com.myapp.event.EventItinerary#39987ocb",
"com.myapp.event.EventItinerary#cd5t776c",
]
}
My question is what should I put in "XXX" to have the EventItinerary objects have the correct JSON representation?

You need to use a ListProperty so we can traverse into it and the ListProperty should refer to a different PropertyBusinessObject. So this should look roughly like this:
public final ListProperty<EventItinerary, Event> eventItineraryList = new ListProperty<>("eventItineraryList", EventItinerary.class);
Notice the EventItinerary.class which is important. The generic valuegets lost due to erasure. The argument lets us reconstruct the object with the right object types when loading from JSON.'
Also again, for this to work EventItinerary must be a PropertyBusinessObject too.

Related

How to include a nested PropertyBusinessObject in a PropertyBusinessObject json?

My app contains several PropertyBusinessObject entities, and most of them have nested PropertyBusinessObject objects as properties.
For instance, a Note has a parent User which had written the note, so the Note entity contain a Property<User, Note> which is instantiated with the User.class and the name of the property.
Here is the code of the Note Entity:
public class Note extends AbstractEntity
{
public final Property<User, Note> author = new Property<>("author", User.class);
public final Property<String, TarotNote> text = new Property<>("text");
public Note() {}
}
AbstractEntity implements the PropertyBusiness interface and define the methods to be overridden by the entities to properly implements the interface.
And here is the result JSON from PropertyIndex.toJson:
{
"author": "our.app.backend.entity.User#77203809",
"text": "test"
}
Do I need to override the toString method of all my entities to be sure to not have this behavior (seems to be the wrong way...) ? Or (I hope) is there another way?
For your information, the parsing of the Json issued from the server works perfectly fine with nested entities.
This seems like a logic bug in the JSON generation code, I've added code to fix this here: https://github.com/codenameone/CodenameOne/commit/34447f62971d8bb696116f02c97bac9b70de89b6

ClassCastException - java.lang.String cannot be cast to com.codename1.ui.Image

I am consuming a JSON string that contains an image object among other objects. From this I create a PropertyBusinessObject which has a the following
public final Property<EncodedImage, Profile> profilePic = new Property<>("profilePic", EncodedImage.class);
I have created a method in the PropertyBusinessObject
public EncodedImage getProfilePic() {
return profilePic.get();
}
I populate my data into the Property business object as follows:
profile.getPropertyIndex().populateFromMap((Map) profileObject);
When I try to display the image on the form using the following code,
ScaleImageLabel profilePic = new ScaleImageLabel(profile.getProfilePic()) {
#Override
protected Dimension calcPreferredSize() {
Dimension dimension = super.calcPreferredSize();
dimension.setHeight(Math.min(dimension.getHeight(), Display.getInstance().convertToPixels(40)));
return dimension;
}
};
profilePic.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
container.add(BorderLayout.NORTH, profilePic);
I get a ClassCastException
Exception: java.lang.ClassCastException - java.lang.String cannot be cast to com.codename1.ui.Image
Can anyone help me resolve, or suggest another way of consuming the JSON string?
populateFromMap doesn't currently support Base64 images, I'll add that as an option as that use case makes sense. Should be there with the Friday update.

Save result from Objectify in human readable form in datastore

I am trying to create an Eventlog (ORMSLOG in example), that saves events in human readable form in Datastore.
Doing this should write readable event:
List<Device> devices = ofy().transactionless().load().type(Device.class).list();
ORMSLOG.log(ORMSLOG.GET_ALL_DEVICES, "Devices found: " + String.valueOf(devices));
The ORMSLOG is a simple class.
public class ORMSLOG {
public final static String CREATE_DEVICE = "Create Device";
public final static String GET_ALL_DEVICES = "Get all Devices";
public static void log(final String event, final String data) {
ofy().save().entity(new Event(event, data)).now();
}
}
But the data saved in Datastore is not readable and looks like this:
ORMSLOG data
I need to transform the reference to the object into human readable text.
You are just logging the String representation of the objects, which is done by calling the toString method. Since you did not override the toString method in the Device class, you are getting the pointer to the objects. If you override the toString method in your Device class to return whatever state you want to return, you would see a much better result. Most IDEs (e.g. Eclipse) have an option to generate toString method for you.

How to receive Map parameters in Resteasy?

I would like to receive these HTTP parameters (POST) in my Resteasy service:
customFields[my_key]=some_value
customFields[my_key2]=some_value2
Something like this doesn't work:
#Form(prefix="customFields")
Map<String, String> customFields
... what happens here is that on the server the new Map is initialized, and the key for the Map entry is set (i.e. "my_key") but value is not set.
Does anyone know how to handle the case like mine, where I need to receive unknown number of fields (within a Map), but each of them properly structured (HTTP map/dictionary notation).
This is a known bug. The workaround is to use your own string wrapper as the map value type. For example:
public class StringWrapper implements Serializable {
private static final long serialVersionUID = 1L;
#FormParam("value")
public String value;
}
Redefine your map as:
#Form(prefix="customFields")
Map<String, StringWrapper> customFields;
And then pass the values to it as customFields[my_key].value=some_value

How to know what class is being deserialized in JackSon Deserializer?

I'm using app engine datastore so I have entity like this.
#PersistenceCapable
public class Author {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
#JsonProperty("id")
#JsonSerialize(using = JsonKeySerializer.class)
#JsonDeserialize(using = JsonKeyDeserializer.class)
private Key key;
....
}
When the model is sent to view, it will serialize the Key object as an Id value. Then, if I send data back from view I want to deserialize the Id back to Key object by using JsonKeyDeserializer class.
public class JsonKeyDeserializer extends JsonDeserializer<Key> {
#Override
public Key deserialize(JsonParser jsonParser, DeserializationContext deserializeContext)
throws IOException, JsonProcessingException {
String id = jsonParser.getText();
if (id.isEmpty()) {
return null;
}
// Here is the problem because I have several entities and I can't fix the Author class in this deserializer like this.
// I want to know what class is being deserialized at runtime.
// return KeyFactory.createKey(Author.class.getSimpleName(), Integer.parseInt(id))
}
}
I tried to debug the value in deserialize's parameters but I can't find the way to get the target deserialized class. How can I solve this?
You may have misunderstood the role of KeySerializer/KeyDeserializer: they are used for Java Map keys, and not as generic identifiers in database sense of term "key".
So you probably would need to use regular JsonSerializer/JsonDeserializer instead.
As to type: it is assumed that handlers are constructed for specific types, and no extra type information is passed during serialization or deserialization process: expected type (if handlers are used for different types) must be passed during construction.
When registering general serializers or deserializers, you can do this when implementing Module, as one of the arguments is type for which (de)serializer is requested.
When defining handlers directly for properties (like when using annotations), this information is available on createContextual() callback of interface ContextualSerializer (and -Deserializer), if your handler implements it: BeanProperty is passed to specify property (in this case field with annotation), and you can access its type. This information needs to be stored to be used during (de)serialization.
EDIT: as author pointed out, I actually misread the question: KeySerializer is the class name, not annotation.

Resources