It's not clear if you're using Jackson, or Gson for your JSON serialization framework, and these will behave differently in this example.
The bottom line here is that if you use any framework in conjunction with JavaFX properties, it needs to fully support and respect encapsulation. Both Lombok (which makes assumptions about the relationship between your fields and property methods), and Gson (which bypasses property methods entirely) do not support encapsulation to the extent needed.
The property naming pattern that JavaFX properties expect is this:
public class MyDAO {
// ...
private final BooleanProperty active = new SimpleBooleanProperty();
public BooleanProperty activeProperty() {
return active ;
}
public final boolean isActive() {
return activeProperty().get();
}
public final void setActive(boolean active) {
activeProperty().set(active);
}
}
From a Java Bean properties perspective (i.e. a perspective that properly supports encapsulation), this class has a boolean
readable-writable property called active
. The fact that is implemented using a JavaFX property is essentially an implementation detail of the class (albeit one which provides additional functionality via the activeProperty()
method).
Lombok simply assumes you want get
and/or set
methods for your fields that define properties of the same type (and name), which simply doesn't work in this case. So my advice would be to not use Lombok for this class (actually, my advice would be never to use Lombok for these very reasons, but that's up to you):
public class MyDAO {
private int id;
private String firstName;
private String lastName;
private final BooleanProperty isActive = new SimpleBooleanProperty();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public BooleanProperty activeProperty() {
return isActive ;
}
public final boolean isActive() {
return activeProperty().get();
}
public final void setActive(boolean active) {
activeProperty().set(active);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + id;
result = prime * result + ((isActive()) ? 0 : 1);
result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyDAO other = (MyDAO) obj;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (id != other.id)
return false;
if (isActive() != other.isActive()) {
return false;
}
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
return true;
}
@Override
public String toString() {
return "MyDAO [getId()=" + getId() + ", getFirstName()=" + getFirstName() + ", getLastName()="
+ getLastName() + ", isActive()=" + isActive() + "]";
}
}
(While that looks like a lot of code, the only part I had to type was the activeProperty()
, isActive()
, and setActive()
methods; the rest was generated in about 10 mouse clicks in Eclipse. E(fx)clipse provides point-and-click functionality for the methods I typed, I just don't have it installed in the version of Eclipse I'm using.)
If you're really wedded to Lombok, I think you can do something like (but, not being a Lombok user, I am not certain if this will work):
@Data
public class MyDAO {
private int id;
private String firstName;
private String lastName;
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
private final BooleanProperty isActive = new SimpleBooleanProperty();
public BooleanProperty activeProperty() {
return isActive ;
}
public final boolean isActive() {
return activeProperty().get();
}
public final void setActive(boolean active) {
activeProperty().set(active);
}
}
Similarly, GSON doesn't respect encapsulation, and tries to replicate your fields instead of your properties (and it appears there's no equivalent to JPA's "field access" versus "property access" functionality, nor any desire to provide it). I favor Jackson for this reason: using Jackson the serialized version is generated via properties, and would look the way you want it directly out of the box:
MyDAO bean = new MyDAO();
bean.setFirstName("Joe");
bean.setLastName("Bloggs");
bean.setActive(false);
StringWriter w = new StringWriter();
ObjectMapper jackson = new ObjectMapper();
jackson.writeValue(w, bean);
System.out.println(w.toString()) ;
// output: {"firstName": "Joe", "lastName":"Bloggs", "active":false}
With GSON, you'll need a type adapter to handle anything using JavaFX properties. (There's probably a way to write a factory that generates type adapters for the properties themselves, but given the number of different possible types (including user-defined implementations of the property interfaces), that's probably pretty difficult to do.)
public class MyDAOTypeAdapter extends TypeAdapter<MyDAO> {
@Override
public void write(JsonWriter out, MyDAO value) throws IOException {
out.beginObject();
out.name("id").value(value.getId());
out.name("firstName").value(value.getFirstName());
out.name("lastName").value(value.getLastName());
out.name("active").value(value.isActive());
out.endObject();
}
@Override
public MyDAO read(JsonReader in) throws IOException {
MyDAO bean = new MyDAO();
in.beginObject();
while (in.hasNext()) {
// should really handle nulls for the strings...
switch(in.nextName()) {
case "id":
bean.setId(in.nextInt());
break ;
case "firstName":
bean.setFirstName(in.nextString());
break ;
case "lastName":
bean.setLastName(in.nextString());
break ;
case "active":
bean.setActive(in.nextBoolean());
break ;
}
}
in.endObject();
return bean ;
}
}
Here's a test for this:
public class Test {
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException {
MyDAO bean = new MyDAO();
ObjectMapper mapper = new ObjectMapper();
bean.setFirstName("Joe");
bean.setLastName("Boggs");
bean.setActive(true);
StringWriter w = new StringWriter();
mapper.writeValue(w, bean);
String output = w.toString() ;
System.out.println("Jackson Serialized version:\n"+output);
MyDAO jacksonBean = mapper.readValue(output, MyDAO.class);
System.out.println("\nJackson Deserialized bean:\n" + jacksonBean);
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(MyDAO.class, new MyDAOTypeAdapter());
gsonBuilder.setPrettyPrinting();
Gson gson = gsonBuilder.create();
w.getBuffer().delete(0, w.getBuffer().length());
gson.toJson(bean, w);
String gsonOutput = w.toString() ;
System.out.println("\nGSON serialized version:\n"+gsonOutput);
MyDAO gsonBean = gson.fromJson(gsonOutput, MyDAO.class);
System.out.println("\nGSON deserialized bean:\n"+gsonBean);
}
}
which generates the following output:
Jackson Serialized version:
{"id":0,"firstName":"Joe","lastName":"Boggs","active":true}
Jackson Deserialized bean:
MyDAO [getId()=0, getFirstName()=Joe, getLastName()=Boggs, isActive()=true]
GSON serialized version:
{
"id": 0,
"firstName": "Joe",
"lastName": "Boggs",
"active": true
}
GSON deserialized bean:
MyDAO [getId()=0, getFirstName()=Joe, getLastName()=Boggs, isActive()=true]