Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java InvalidDefinitionException when serializing object with jackson databind

I'm trying to write the following Player object as String using ObjectMapper from 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;
}

I'm using the following command to do this:

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

unfortunately I'm getting the following relevant error message:

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"])

Like the error says it has something to do with JavaFx not exporting a certain dependency, but since I'm not in control of JavaFx I'm not really sure about how to fix this.

like image 931
Kevin Tinnemans Avatar asked May 25 '19 13:05

Kevin Tinnemans


People also ask

What is jsonmappingexception-no serializer found for class?

5. JsonMappingException: No Serializer Found for Class 5.1. The Problem Now let's take a look at JsonMappingException: No Serializer Found for Class. This exception is thrown if we try to serialize an instance while its properties and their getters are private. We'll try to serialize a UserWithPrivateFields:

Why does Jackson throw this exception when deserializing an abstract class?

This exception is thrown if Jackson can't create an instance of the class, which happens if the class is abstract or it is just an interface. Here we'll try to deserialize an instance from class Zoo that has a property animal with abstract type Animal:

How to fix Jackson API deserialization error when deserializing shop?

Jackson API during deserialization is unable to identify a suitable constructor to create an instance of the class Shop, which doesn’t define a no-argument default constructor. 2.4. Solution Provide a default constructor in the Shop class as shown below to fix this error.

How to serialize Java objects into JSON?

Similar to the converse conversion - the writeValue () method is used to serialize Java objects into JSON. You can write objects to a string, file or output stream. Again, the simplest form your object can be serialized to is a JSON-formatted string:


2 Answers

You're trying to store the Circle class, which is a JavaFX class, that is not really a data class (it is a UI element), with many properties (like radius, thickness, color, fills, borders etc.). As such it is tied in with the JavaFX system in various ways and will not store well.

Instead, just store the information you want in a simple class of your own, which has the information you need to create the Circle object again when you read it back.

like image 69
john16384 Avatar answered Sep 23 '22 06:09

john16384


Generally Jackson works the best with POJO classes. When you want to serialise business objects many unexpected errors could occur. Probably the best solution would be to create new model classes which represents state of Player and Snowball. Something like PlayerState and SnowballState. These two classes should follow POJO rules: getters, setters, no-arg constructor, etc. When you need to save state to JSON you can convert your business model to state model and serialise state model. When you need to deserialise JSON you need to deserialise it to state model and after that convert it to business model. For JavaFX classes you need to implement custom serialisers and deserialisers if needed. They are also not regular POJO classes and needs special treatment.

Lets implement two serialisers and one deserialiser:

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

You can use them as below:

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

Above code prints:

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

As you can see we can serialise and deserialise Player class but it needs many extra work. Also for each getter methods which does business logic I ignored them like below:

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

One more hint: getHint method has side effect. It removes life - whatever it means. That's generally a bad practice but this question is not about naming.

like image 44
Michał Ziober Avatar answered Sep 23 '22 06:09

Michał Ziober