问题
I have added a BooleanProperty into a DAO class which is to be serialized into JSON and sent across to a server to be saved in a MySQL database. The reason I am using the BooleanProperty is because I want to use data binding for the 'isActive' field in my JavaFX desktop application.
The class to be serialized:
package com.example.myapplication
import lombok.Data;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@Data
public class MyDAO {
private int id;
private String firstName;
private String lastname;
private final BooleanProperty isActive = new SimpleBooleanProperty();
}
I am serializing to JSON using Gson:
StringEntity entity = new StringEntity(new Gson().toJson(myDTO), "UTF-8");
When this gets serialized to JSON, it looks like this:
{
"id":0,
"first_name":"Joe",
"last_name":"Bloggs",
"is_active":{
"name":"",
"value":false,
"valid":true
}
}
This is causing problems on the server when deserializing (using Jackson), as the server expects a boolean value to correspond with what will be saved in the database. Is there a way to just get the true/false value deserialized from the BooleanProperty?
This is what I would like to see in the server:
{
"id":0,
"first_name":"Joe",
"last_name":"Bloggs",
"is_active": false,
}
回答1:
Your client app uses Gson
to serialise POJO
to JSON
and server app uses Jackson
to deserialise JSON
back to POJO
. In both cases these two libraries by default serialise provided classes as regular POJO
-s. In your POJO
you use JavaFX Properties
which are wrappers for values with extra functionality. When you serialise POJO
to JSON
sometimes you need to hide internal implementation of POJO
and this is why you should implement custom serialiser or use FX Gson library.
1. Custom serialiser
To write custom serialiser you need to implement com.google.gson.JsonSerializer
interface. Below you can find an example hot it could look like:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import lombok.Data;
import java.lang.reflect.Type;
public class GsonApp {
public static void main(String[] args) {
MyDAO myDAO = new MyDAO();
myDAO.setId(1);
myDAO.setFirstName("Vika");
myDAO.setLastname("Zet");
myDAO.getIsActive().set(true);
Gson gson = new GsonBuilder()
.registerTypeAdapter(BooleanProperty.class, new BooleanPropertySerializer())
.setPrettyPrinting().create();
System.out.println(gson.toJson(myDAO));
}
}
class BooleanPropertySerializer implements JsonSerializer<BooleanProperty> {
@Override
public JsonElement serialize(BooleanProperty src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.getValue());
}
}
@Data
class MyDAO {
private int id;
private String firstName;
private String lastname;
private final BooleanProperty isActive = new SimpleBooleanProperty();
}
Above code prints:
{
"id": 1,
"firstName": "Vika",
"lastname": "Zet",
"isActive": true
}
2. FX Gson
In case you use many types from javafx.beans.property.*
package good idea would be to use FX Gson
library which implement custom serialisers for most used types. You just need to add one extra dependency to your Maven
POM
file:
<dependency>
<groupId>org.hildan.fxgson</groupId>
<artifactId>fx-gson</artifactId>
<version>3.1.2</version>
</dependency>
Example usage:
import com.google.gson.Gson;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import lombok.Data;
import org.hildan.fxgson.FxGson;
public class GsonApp {
public static void main(String[] args) {
MyDAO myDAO = new MyDAO();
myDAO.setId(1);
myDAO.setFirstName("Vika");
myDAO.setLastname("Zet");
myDAO.getIsActive().set(true);
Gson gson = FxGson.coreBuilder().setPrettyPrinting().create();
System.out.println(gson.toJson(myDAO));
}
}
Above code prints:
{
"id": 1,
"firstName": "Vika",
"lastname": "Zet",
"isActive": true
}
回答2:
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]
回答3:
I believe the answer is to mark the field you don't want to be serialized as transient
and add the boolean field containing the value.
@Data
public class MyDAO {
private int id;
private String firstName;
private String lastname;
private transient final BooleanProperty booleanProp = new SimpleBooleanProperty();
private boolean isActive = booleanProp.get();
}
The main issue is that you are delegating the responsibility of serializing your objects to a 3rd party. You could also serialize it yourself to control the serialization behavior. It will get tricky if you want to do something like serialize the boolean
value when it is false
but the actual BooleanProperty
object when the value is true
. That is, unless BooleanProperty
does that by default, which it does not.
来源:https://stackoverflow.com/questions/60455801/jackson-gson-serialize-and-deserialize-javafx-properties-to-json