Jackson/Gson将JavaFX属性序列化并反序列化为json

Jackson/Gson将JavaFX属性序列化并反序列化为json,java,javafx,jackson,gson,json-deserialization,Java,Javafx,Jackson,Gson,Json Deserialization,我在DAO类中添加了一个BooleanProperty,该类将被序列化为JSON并发送到服务器,保存在MySQL数据库中。我之所以使用BooleanProperty,是因为我想在JavaFX桌面应用程序中为“isActive”字段使用数据绑定 要序列化的类: package com.example.myapplication import lombok.Data; import javafx.beans.property.BooleanProperty; import javafx.beans

我在DAO类中添加了一个BooleanProperty,该类将被序列化为JSON并发送到服务器,保存在MySQL数据库中。我之所以使用BooleanProperty,是因为我想在JavaFX桌面应用程序中为“isActive”字段使用数据绑定

要序列化的类:

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();
}
我正在使用Gson序列化为JSON:

StringEntity entity = new StringEntity(new Gson().toJson(myDTO), "UTF-8");
将其序列化为JSON时,如下所示:

{
   "id":0,
   "first_name":"Joe",
   "last_name":"Bloggs",
   "is_active":{
      "name":"",
      "value":false,
      "valid":true

   }
}
这会在反序列化(使用Jackson)时在服务器上造成问题,因为服务器希望布尔值与将保存在数据库中的值相对应。有没有办法从BooleanProperty反序列化真/假值

这是我希望在服务器中看到的内容:

{
   "id":0,
   "first_name":"Joe",
   "last_name":"Bloggs",
   "is_active": false,
}

我相信答案是将不希望序列化的字段标记为transient,并添加包含该值的布尔字段

@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();
}

主要问题是,您正在将序列化对象的责任委托给第三方。您还可以自己对其进行序列化,以控制序列化行为。如果要序列化
布尔值(当值为
false
时)和实际的
布尔属性(当值为
true
时)之类的操作,这将变得很棘手。也就是说,除非默认情况下,
BooleanProperty
会这样做,否则它不会这样做。

不清楚您是在JSON序列化框架中使用Jackson还是Gson,在本例中,它们的行为会有所不同

这里的底线是,如果您将任何框架与JavaFX属性结合使用,它需要完全支持和尊重封装。Lombok(对字段和属性方法之间的关系进行假设)和Gson(完全绕过属性方法)都不支持所需的封装

JavaFX属性期望的属性命名模式是:

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);
    }

}
从JavaBean属性的角度(即正确支持封装的角度)来看,这个类有一个名为
active
boolean
可读写属性。使用JavaFX属性实现的事实本质上是类的一个实现细节(尽管它通过
activeProperty()
方法提供了额外的功能)

Lombok只是假设您希望为定义相同类型(和名称)属性的字段使用
get
和/或
set
方法,但在本例中,这根本不起作用。所以我的建议是不要在这个课程中使用Lombok(实际上,我的建议是永远不要因为这些原因而使用Lombok,但这取决于你):

(虽然这看起来像是很多代码,但我必须键入的唯一部分是
activeProperty()
isActive()
,和
setActive()
方法;其余部分在Eclipse.E(fx)中大约10次鼠标点击后生成。)clipse为我键入的方法提供了点击功能,我只是没有在我使用的Eclipse版本中安装它。)

如果你真的爱上了Lombok,我想你可以做一些类似的事情(但是,不是Lombok用户,我不确定这是否有效):

类似地,GSON不尊重封装,尝试复制字段而不是属性(而且似乎没有JPA的“字段访问”与“属性访问”功能的等价物,也没有任何提供它的愿望)。我之所以喜欢Jackson,是因为:使用Jackson,序列化版本是通过属性生成的,并且看起来会像您希望的那样直接开箱即用:

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}
使用GSON,您需要一个类型适配器来处理使用JavaFX属性的任何内容。(可能有一种方法可以编写一个工厂,为属性本身生成类型适配器,但考虑到不同类型的数量(包括属性接口的用户定义实现),这可能非常困难。)

这将生成以下输出:

Jackson序列化版本:
{“id”:0,“firstName”:“Joe”,“lastName”:“Boggs”,“active”:true}
Jackson反序列化bean:
MyDAO[getId()=0,getFirstName()=Joe,getLastName()=Boggs,isActive()=true]
GSON序列化版本:
{
“id”:0,
“名字”:“乔”,
“姓氏”:“博格斯”,
“活动”:真
}
GSON反序列化bean:
MyDAO[getId()=0,getFirstName()=Joe,getLastName()=Boggs,isActive()=true]

您的客户端应用程序使用
Gson
POJO
序列化为
JSON
,服务器应用程序使用
Jackson
JSON
反序列化回
POJO
。在这两种情况下,这两个库默认将提供的类序列化为常规的
POJO
-s。在
POJO
中使用
JavaFX属性
,这些属性是具有额外功能的值的包装。当您将
POJO
序列化为
JSON
时,有时需要隐藏
POJO
的内部实现,这就是为什么您应该实现自定义序列化器或使用库的原因

1.自定义串行器 要编写自定义序列化程序,您需要实现
com.google.gson.JsonSerializer
接口。下面您可以找到一个示例,它可能看起来像:

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();
}
2.FX Gson 如果您使用
javafx.beans.property.*
包中的许多类型,最好使用
FX Gson
库,该库为大多数使用的类型实现自定义序列化程序。您只需向
Maven
POM
文件中添加一个额外文件:

<dependency>
    <groupId>org.hildan.fxgson</groupId>
    <artifactId>fx-gson</artifactId>
    <version>3.1.2</version>
</dependency>
以上代码打印:

{
  "id": 1,
  "firstName": "Vika",
  "lastname": "Zet",
  "isActive": true
}
{
  "id": 1,
  "firstName": "Vika",
  "lastname": "Zet",
  "isActive": true
}

将序列化代码共享到JSONUpdate中。那么,您是否使用Jackson或Gson进行JSON序列化?我不是JSON序列化专家,但我认为Jackson会立即正确处理此问题,前提是您的属性访问器方法遵循标准JavaFX模式。这个问题很可能是因为龙目巨人不知道
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();
}
{
  "id": 1,
  "firstName": "Vika",
  "lastname": "Zet",
  "isActive": true
}
<dependency>
    <groupId>org.hildan.fxgson</groupId>
    <artifactId>fx-gson</artifactId>
    <version>3.1.2</version>
</dependency>
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));
    }

}
{
  "id": 1,
  "firstName": "Vika",
  "lastname": "Zet",
  "isActive": true
}