Java InvalidDefinitionException при сериализации объекта с привязкой данных Джексона

Я пытаюсь написать следующий объект Player как String, используя ObjectMapper от Jackson.

package models.Game;

import models.Game.Enums.SnowballState;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;

import java.util.ArrayList;
import java.util.List;

public class Player {
    private Circle circle;
    private String name;
    private Color color;
    private int points = 0;
public int getLives() {
    return lives;
}

private int lives = 3;
private List<Snowball> snowballs;
private Circle oldCircle;
private int stepSize = 10;

public Player(String name, Color color) {
    this.name = name;
    circle = new Circle();
    oldCircle = new Circle();
    this.color = color;
    snowballs = new ArrayList<>();
    snowballs.add(new Snowball(this));
    snowballs.add(new Snowball(this));
    snowballs.add(new Snowball(this));
}

public Player() {

}

private void removeLife() {
    this.lives--;
}

public int getHit() {
    removeLife();
    return getLives();
}

public int shotSuccess() {
    points+= 50;
    return points;
}

public int getSnowballAmount() {
    int balls = 0;
    for (Snowball ball : snowballs) {
        if (ball.getState() == SnowballState.CREATED) {
            balls++;
        }
    }
    return balls;
}

public List<Snowball> getSnowballs() {
    return snowballs;
}

public Snowball getNextSnowball() {
    for (Snowball ball : snowballs) {
        if (ball.getState() == SnowballState.CREATED) {
            return ball;
        }
    }
    return null;
}

public void createSnowball() {
    if (getSnowballAmount() < 3) {
        snowballs.add(new Snowball(this));
    }
}

public Color getColor() {
    return this.color;
}

public Circle getCircle() {
    return this.circle;
}

public void moveLeft() {
    saveOld();
    circle.setTranslateX(circle.getTranslateX() - stepSize);
}

public void moveRight() {
    saveOld();
    circle.setTranslateX(circle.getTranslateX() + stepSize);
}

public void moveUp() {
    saveOld();
    circle.setTranslateY(circle.getTranslateY() - stepSize);
}

public void moveDown() {
    saveOld();
    circle.setTranslateY(circle.getTranslateY() + stepSize);
}

public void undo() {
    circle.setTranslateX(oldCircle.getTranslateX());
    circle.setTranslateY(oldCircle.getTranslateY());
}

private void saveOld() {
    oldCircle.setTranslateX(circle.getTranslateX());
    oldCircle.setTranslateY(circle.getTranslateY());
}

public Snowball shootSnowball(Snowball ball, double mouseX, double mouseY) {

    double polarDirection = Math.atan2(mouseY - circle.getTranslateY(), mouseX - circle.getTranslateX() + 50);
    ball.setState(SnowballState.ALIVE);
    ball.setDirection(polarDirection);
    ball.getCircle().setTranslateX(circle.getTranslateX() + 50);
    ball.getCircle().setTranslateY(circle.getTranslateY());
    return ball;
}

Для этого я использую следующую команду:

 String json = null;
        try {
            json = objectMapper.writeValueAsString(instanceOfPlayerClass);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

к сожалению, я получаю следующее соответствующее сообщение об ошибке:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type com.sun.javafx.scene.NodeEventDispatcher: Failed to construct BeanSerializer for [simple type, class com.sun.javafx.scene.NodeEventDispatcher]: (java.lang.reflect.InaccessibleObjectException) Unable to make public final com.sun.javafx.event.BasicEventDispatcher com.sun.javafx.event.BasicEventDispatcher.getPreviousDispatcher() accessible: module javafx.base does not "exports com.sun.javafx.event" to module com.fasterxml.jackson.databind (through reference chain: models.communication.Websockets.ConnectionSubmitModel["player"]->models.Game.Player["circle"]->javafx.scene.shape.Circle["parent"]->javafx.scene.layout.GridPane["parent"]->javafx.scene.layout.AnchorPane["eventDispatcher"])

Как говорится в ошибке, это как-то связано с тем, что JavaFx не экспортирует определенную зависимость, но, поскольку я не контролирую JavaFx, я не совсем уверен, как это исправить.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
7
0
6 543
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы пытаетесь сохранить класс Circle, который является классом JavaFX, который на самом деле не является классом данных (это элемент пользовательского интерфейса) со многими свойствами (такими как радиус, толщина, цвет, заливка, границы и т. д.). Таким образом, он по-разному связан с системой JavaFX и не будет хорошо храниться.

Вместо этого просто сохраните нужную информацию в собственном простом классе, в котором есть информация, необходимая для повторного создания объекта Circle, когда вы его читаете.

Хорошо подумав об этом, мне даже не нужно сериализовать объект Circle, но после прочтения этого добавление ключевого слова «transient», похоже, не помогает

Kevin Tinnemans 25.05.2019 16:02

Класс Color может дать ту же проблему. Кроме того, вы должны использовать @JsonIgnore в геттере вместо transient, потому что Джексон по умолчанию смотрит на геттеры, чтобы определить, что сериализовать (вы также можете настроить его для просмотра полей, если хотите... В этом случае он может уважать transient ). См. этот ответ для получения дополнительной информации об этом: stackoverflow.com/questions/9112900/…

john16384 25.05.2019 16:08

Как правило, Jackson лучше всего работает с POJO классами. Если вы хотите сериализовать бизнес-объекты, может возникнуть множество непредвиденных ошибок. Вероятно, лучшим решением было бы создать новые классы моделей, которые представляют состояниеPlayer и Snowball. Что-то вроде PlayerState и SnowballState. Эти два класса должны следовать правилам POJO: getters, setters, no-arg constructor и т. д. Когда вам нужно сохранить состояние в JSON, вы можете преобразовать Бизнес модель в государственная модель и сериализовать государственная модель. Когда вам нужно десериализовать JSON, вам нужно десериализовать его в государственная модель, а затем преобразовать в Бизнес модель. Для классов JavaFX вам необходимо реализовать собственные сериализаторы и десериализаторы, если это необходимо. Они также не являются регулярными POJO занятиями и требуют особого отношения.

Давайте реализуем два сериализатора и один десериализатор:

class CircleJsonSerializer extends JsonSerializer<Circle> {

    @Override
    public void serialize(Circle value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeStartObject();
        gen.writeNumberField("radius", value.getRadius());
        gen.writeNumberField("centerX", value.getCenterX());
        gen.writeNumberField("centerY", value.getCenterY());
        gen.writeEndObject();
    }
}

class CircleJsonDeserializer extends JsonDeserializer<Circle> {

    @Override
    public Circle deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        TreeNode node = p.readValueAsTree();
        NumericNode radius = (NumericNode) node.get("radius");
        NumericNode centerX = (NumericNode) node.get("centerX");
        NumericNode centerY = (NumericNode) node.get("centerY");

        return new Circle(centerX.doubleValue(), centerY.doubleValue(), radius.doubleValue());
    }
}

class ColorJsonDeserializer extends JsonDeserializer<Color> {
    @Override
    public Color deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        TreeNode node = p.readValueAsTree();
        NumericNode red = (NumericNode) node.get("red");
        NumericNode green = (NumericNode) node.get("green");
        NumericNode blue = (NumericNode) node.get("blue");
        NumericNode opacity = (NumericNode) node.get("opacity");

        return Color.color(red.doubleValue(), green.doubleValue(), blue.doubleValue(), opacity.doubleValue());
    }
}

Вы можете использовать их, как показано ниже:

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.NumericNode;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        Player player = new Player("N1", Color.BLUE);

        SimpleModule javafxModule = new SimpleModule();
        javafxModule.addSerializer(Circle.class, new CircleJsonSerializer());
        javafxModule.addDeserializer(Circle.class, new CircleJsonDeserializer());
        javafxModule.addDeserializer(Color.class, new ColorJsonDeserializer());

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(javafxModule);
        mapper.enable(SerializationFeature.INDENT_OUTPUT);

        String json = mapper.writeValueAsString(player);
        System.out.println(json);
        System.out.println(mapper.readValue(json, Player.class));
    }
}

Над кодом печатается:

{
  "circle" : {
    "radius" : 1.0,
    "centerX" : 0.0,
    "centerY" : 0.0
  },
  "color" : {
    "red" : 0.0,
    "green" : 0.0,
    "blue" : 1.0,
    "opacity" : 1.0,
    "opaque" : true,
    "hue" : 240.0,
    "saturation" : 1.0,
    "brightness" : 1.0
  },
  "lives" : 3,
  "snowballs" : [ {
    "state" : "CREATED",
    "direction" : 0.0,
    "circle" : null
  }, {
    "state" : "CREATED",
    "direction" : 0.0,
    "circle" : null
  }, {
    "state" : "CREATED",
    "direction" : 0.0,
    "circle" : null
  } ]
}

//ToString
Player{circle=Circle[centerX=0.0, centerY=0.0, radius=1.0, fill=0x000000ff], name='null', color=0x0000ffff, points=0, lives=3, snowballs=[Snowball{player=null, state=CREATED, direction=0.0, circle=null}, Snowball{player=null, state=CREATED, direction=0.0, circle=null}, Snowball{player=null, state=CREATED, direction=0.0, circle=null}], oldCircle=null, stepSize=10}

Как видите, мы можем сериализовать и десериализовать класс Player, но для этого потребуется много дополнительной работы. Также для каждого метода getter, который выполняет бизнес-логику, я проигнорировал их, как показано ниже:

@JsonIgnore
public int getHit() {
    removeLife();
    return getLives();
}

Еще одна подсказка: у метода getHint есть побочный эффект. Он убирает жизнь — что бы это ни значило. Как правило, это плохая практика, но этот вопрос не об именовании.

Другие вопросы по теме