Джексон / Гсон сериализует и десериализует свойства JavaFX до json - PullRequest
1 голос
/ 28 февраля 2020

Я добавил BooleanProperty в класс DAO, который должен быть сериализован в JSON и отправлен на сервер для сохранения в базе данных MySQL. Причина, по которой я использую BooleanProperty, заключается в том, что я хочу использовать привязку данных для поля «isActive» в моем настольном приложении JavaFX.

Класс для сериализации:

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

Я сериализация в JSON с использованием Gson:

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

   }
}

Это вызывает проблемы на сервере при десериализации ( используя Джексона), поскольку сервер ожидает, что логическое значение будет соответствовать тому, что будет сохранено в базе данных. Есть ли способ просто получить значение true / false, десериализованное из BooleanProperty?

Вот что я хотел бы видеть на сервере:

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

Ответы [ 3 ]

2 голосов
/ 29 февраля 2020

Ваше клиентское приложение использует 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. 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>

Пример использования:

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
}
1 голос
/ 29 февраля 2020

Не ясно, используете ли вы Джексон или Gson для своей JSON среды сериализации, и в этом примере они будут вести себя по-другому.

Суть в том, что если вы используете какую-либо инфраструктуру в сочетании со свойствами JavaFX, она должна полностью поддерживать и соблюдать инкапсуляцию. И Lombok (который делает предположения о взаимосвязи между вашими полями и методами свойств), и Gson (который полностью обходит методы свойств) не поддерживают инкапсуляцию в необходимой степени.

Шаблон именования свойств, ожидаемый свойствами JavaFX, 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);
    }

}

С точки зрения свойств Java Bean (т. е. перспективы, которая должным образом поддерживает инкапсуляцию), этот класс имеет свойство boolean для чтения и записи, называемое active. Тот факт, что реализован с использованием свойства JavaFX, по сути является деталью реализации класса (хотя и предоставляющего дополнительные функциональные возможности с помощью метода activeProperty()).

Lombok просто предполагает, что вы хотите get и / или set методы для ваших полей , которые определяют свойства того же типа (и имени), что просто не работает в этом случае. Поэтому мой совет - не использовать 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(); остальная часть была сгенерирована примерно за 10 щелчков мыши в Eclipse. 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 «доступ к полям» и «доступ к свойствам», а также никакого желания предоставить ее). Я предпочитаю Джексона по этой причине: используя Джексона, сериализованная версия генерируется через 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]
0 голосов
/ 28 февраля 2020

Я полагаю, что ответ состоит в том, чтобы пометить поле, которое вы не хотите сериализовать, как 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();
}

Основная проблема заключается в том, что вы делегируете ответственность за сериализацию ваших объектов третьим лицам. Вы также можете сами сериализовать его, чтобы контролировать поведение сериализации. Это будет сложно, если вы захотите сделать что-то вроде сериализации значения boolean, когда оно равно false, но фактического объекта BooleanProperty, когда значение равно true. То есть, если BooleanProperty не делает этого по умолчанию, а это не так.

...