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]

您的客户端应用程序使用 GsonPOJO 序列化为 JSON,服务器应用程序使用 JacksonJSON 反序列化为 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
}