简体   繁体   English

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

[英]Jackson/Gson serialize and deserialize JavaFX Properties to json

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.我已将 BooleanProperty 添加到 DAO 类中,该类将被序列化为 JSON 并发送到服务器以保存在 MySQL 数据库中。 The reason I am using the BooleanProperty is because I want to use data binding for the 'isActive' field in my JavaFX desktop application.我使用 BooleanProperty 的原因是我想在我的 JavaFX 桌面应用程序中对“isActive”字段使用数据绑定。

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

StringEntity entity = new StringEntity(new Gson().toJson(myDTO), "UTF-8");

When this gets serialized to JSON, it looks like this:当它被序列化为 JSON 时,它看起来像这样:

{
   "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.这会在反序列化(使用 Jackson)时导致服务器出现问题,因为服务器期望布尔值与将保存在数据库中的内容相对应。 Is there a way to just get the true/false value deserialized from the BooleanProperty?有没有办法从 BooleanProperty 反序列化真/假值?

This is what I would like to see in the server:这是我希望在服务器中看到的:

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

Your client app uses Gson to serialise POJO to JSON and server app uses Jackson to deserialise JSON back to POJO .您的客户端应用程序使用Gson来还原序列化POJOJSON和服务器应用程序使用Jackson到deserialise JSONPOJO In both cases these two libraries by default serialise provided classes as regular POJO -s.在这两种情况下,这两个库默认将提供的类序列化为常规POJO -s。 In your POJO you use JavaFX Properties which are wrappers for values with extra functionality.在您的POJO您使用JavaFX Properties ,它们是具有额外功能的值的包装器。 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.当您将POJO序列POJO JSON有时您需要隐藏POJO内部实现,这就是您应该实现自定义序列化器或使用FX Gson库的原因。

1. Custom serialiser 1.自定义序列化器

To write custom serialiser you need to implement com.google.gson.JsonSerializer interface.要编写自定义序列化器,您需要实现com.google.gson.JsonSerializer接口。 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 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.如果您使用javafx.beans.property.*包中的多种类型,好主意是使用FX Gson库,它为大多数使用的类型实现自定义序列化程序。 You just need to add one extra dependency to your Maven POM file:您只需要向Maven POM文件添加一个额外的依赖项

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

It's not clear if you're using Jackson, or Gson for your JSON serialization framework, and these will behave differently in this example.不清楚您的 JSON 序列化框架是使用 Jackson 还是 Gson,并且在本示例中它们的行为会有所不同。

The bottom line here is that if you use any framework in conjunction with JavaFX properties, it needs to fully support and respect encapsulation.这里的底线是,如果您将任何框架与 JavaFX 属性结合使用,它需要完全支持和尊重封装。 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. Lombok(对字段和属性方法之间的关系进行假设)和 Gson(完全绕过属性方法)都不支持所需程度的封装。

The property naming pattern that JavaFX properties expect is this: 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);
    }

}

From a Java Bean properties perspective (ie a perspective that properly supports encapsulation), this class has a boolean readable-writable property called active .从 Java Bean 属性的角度(即正确支持封装的角度),这个类有一个boolean可读可写属性,称为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).使用 JavaFX 属性实现的事实本质上是类的实现细节(尽管它通过activeProperty()方法提供了附加功能)。

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. Lombok 只是假设您想要为定义相同类型(和名称)属性字段get和/或set方法,这在这种情况下根本不起作用。 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):所以我的建议是不要在这门课上使用 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() + "]";
    }


}

(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.) (虽然代码看起来很多,但我唯一需要输入的部分是activeProperty()isActive()setActive()方法;其余部分是在 Eclipse 中通过大约 10 次鼠标点击生成的。E(fx)clipse为我输入的方法提供点击功能,我只是没有在我使用的 Eclipse 版本中安装它。)

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):如果你真的和 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);
    }
}

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).同样,GSON 不尊重封装,并尝试复制您的字段而不是您的属性(并且似乎没有等同于 JPA 的“字段访问”与“属性访问”功能,也没有提供它的任何愿望)。 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:由于这个原因,我喜欢 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}

With GSON, you'll need a type adapter to handle anything using JavaFX properties.使用 GSON,您需要一个类型适配器来处理使用 JavaFX 属性的任何内容。 (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]

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.我相信答案是将您不想序列化的字段标记为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();
}

The main issue is that you are delegating the responsibility of serializing your objects to a 3rd party.主要问题是您将序列化对象的责任委托给第 3 方。 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 .如果你想做一些事情,比如在boolean值为false时序列化boolean值,但在值为true时序列化实际的BooleanProperty对象,这BooleanProperty That is, unless BooleanProperty does that by default, which it does not.也就是说,除非BooleanProperty默认情况下这样做,否则它不会。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM