Jackson/Gson 将 JavaFX 属性序列化和反序列化为 json
Jackson/Gson serialize and deserialize JavaFX Properties to json
我已将 BooleanProperty 添加到 DAO class 中,它将序列化为 JSON 并发送到服务器以保存在 MySQL 数据库中。我使用 BooleanProperty 的原因是因为我想在我的 JavaFX 桌面应用程序中为 'isActive' 字段使用数据绑定。
待序列化的class:
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 中反序列化 true/false 值?
这是我希望在服务器中看到的:
{
"id":0,
"first_name":"Joe",
"last_name":"Bloggs",
"is_active": false,
}
尚不清楚您是否将 Jackson 或 Gson 用于 JSON 序列化框架,并且在本例中它们的行为会有所不同。
这里的底线是,如果您将任何框架与 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);
}
}
从 Java Bean 属性的角度(即正确支持封装的角度)来看,这个 class 有一个 boolean
可读可写的 属性 称为 active
.使用 JavaFX 属性 实现的事实本质上是 class 的实现细节(尽管它通过 activeProperty()
方法提供了额外的功能)。
Lombok 只是假设您需要 get
and/or set
方法用于定义 属性 [=54] 的 字段 =] 相同类型(和名称),在这种情况下根本不起作用。因此,我的建议是不要为此 class 使用 Lombok(实际上,出于这些原因,我的建议是 永远不要 使用 Lombok,但这取决于您):
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() + "]";
}
}
(虽然这看起来像很多代码,但我唯一需要输入的部分是 activeProperty()
、isActive()
和 setActive()
方法;其余部分大约在在 Eclipse 中单击 10 次鼠标。E(fx)clipse 为我键入的方法提供了点击功能,我只是没有在我使用的 Eclipse 版本中安装它。)
如果你真的喜欢 Lombok,我 认为 你可以做类似的事情(但是,不是 Lombok 用户,我不确定这是否有效):
@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);
}
}
类似地,GSON 不尊重封装,并尝试复制您的 字段 而不是您的 属性 (而且似乎没有相当于 JPA 的 "field access" 与 "property access" 功能,也不希望提供它)。我喜欢 Jackson 的原因是:使用 Jackson,序列化版本是通过 properties 生成的,开箱即用,看起来就像你想要的那样:
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 属性的任何内容。 (可能有一种方法可以编写一个为属性本身生成类型适配器的工厂,但是考虑到可能的不同类型的数量(包括 属性 接口的用户定义实现),这可能很难做到。)
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 ;
}
}
这是一个测试:
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);
}
}
生成以下输出:
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]
您的客户端应用程序使用 Gson
将 POJO
序列化为 JSON
,服务器应用程序使用 Jackson
将 JSON
反序列化为 POJO
.在这两种情况下,这两个库默认序列化提供 类 作为常规 POJO
-s。在您的 POJO
中,您使用 JavaFX Properties
这是具有额外功能的值的包装器。当您将 POJO
序列化为 JSON
时,有时您需要隐藏 POJO
的内部实现,这就是您应该实现自定义序列化程序或使用 FX Gson 库的原因。
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();
}
以上代码打印:
{
"id": 1,
"firstName": "Vika",
"lastname": "Zet",
"isActive": true
}
2。外汇 Gson
如果您使用 javafx.beans.property.*
包中的许多类型,好主意是使用 FX Gson
库,它为大多数使用的类型实现自定义序列化程序。您只需要添加一个额外的 dependency 到您的 Maven
POM
文件:
<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
}
我已将 BooleanProperty 添加到 DAO class 中,它将序列化为 JSON 并发送到服务器以保存在 MySQL 数据库中。我使用 BooleanProperty 的原因是因为我想在我的 JavaFX 桌面应用程序中为 'isActive' 字段使用数据绑定。
待序列化的class:
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 中反序列化 true/false 值?
这是我希望在服务器中看到的:
{
"id":0,
"first_name":"Joe",
"last_name":"Bloggs",
"is_active": false,
}
尚不清楚您是否将 Jackson 或 Gson 用于 JSON 序列化框架,并且在本例中它们的行为会有所不同。
这里的底线是,如果您将任何框架与 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);
}
}
从 Java Bean 属性的角度(即正确支持封装的角度)来看,这个 class 有一个 boolean
可读可写的 属性 称为 active
.使用 JavaFX 属性 实现的事实本质上是 class 的实现细节(尽管它通过 activeProperty()
方法提供了额外的功能)。
Lombok 只是假设您需要 get
and/or set
方法用于定义 属性 [=54] 的 字段 =] 相同类型(和名称),在这种情况下根本不起作用。因此,我的建议是不要为此 class 使用 Lombok(实际上,出于这些原因,我的建议是 永远不要 使用 Lombok,但这取决于您):
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() + "]";
}
}
(虽然这看起来像很多代码,但我唯一需要输入的部分是 activeProperty()
、isActive()
和 setActive()
方法;其余部分大约在在 Eclipse 中单击 10 次鼠标。E(fx)clipse 为我键入的方法提供了点击功能,我只是没有在我使用的 Eclipse 版本中安装它。)
如果你真的喜欢 Lombok,我 认为 你可以做类似的事情(但是,不是 Lombok 用户,我不确定这是否有效):
@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);
}
}
类似地,GSON 不尊重封装,并尝试复制您的 字段 而不是您的 属性 (而且似乎没有相当于 JPA 的 "field access" 与 "property access" 功能,也不希望提供它)。我喜欢 Jackson 的原因是:使用 Jackson,序列化版本是通过 properties 生成的,开箱即用,看起来就像你想要的那样:
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 属性的任何内容。 (可能有一种方法可以编写一个为属性本身生成类型适配器的工厂,但是考虑到可能的不同类型的数量(包括 属性 接口的用户定义实现),这可能很难做到。)
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 ;
}
}
这是一个测试:
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);
}
}
生成以下输出:
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]
您的客户端应用程序使用 Gson
将 POJO
序列化为 JSON
,服务器应用程序使用 Jackson
将 JSON
反序列化为 POJO
.在这两种情况下,这两个库默认序列化提供 类 作为常规 POJO
-s。在您的 POJO
中,您使用 JavaFX Properties
这是具有额外功能的值的包装器。当您将 POJO
序列化为 JSON
时,有时您需要隐藏 POJO
的内部实现,这就是您应该实现自定义序列化程序或使用 FX Gson 库的原因。
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();
}
以上代码打印:
{
"id": 1,
"firstName": "Vika",
"lastname": "Zet",
"isActive": true
}
2。外汇 Gson
如果您使用 javafx.beans.property.*
包中的许多类型,好主意是使用 FX Gson
库,它为大多数使用的类型实现自定义序列化程序。您只需要添加一个额外的 dependency 到您的 Maven
POM
文件:
<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
}