I am currently programming a game in Java, and I am having a problem with saving the entities data in Jackson. I need to (de-) serialize a HashMap with different subclasses of a superclass. E.g. I have a class called Mob
and every entity, like Player
for example, is a subclass of Mob
:
public HashMap<Byte, Mob> mobs = new Hashmap<>(); // Byte is ok here, as I don't have many mobs yet and it can also be changed to whatever I need to
public class Player extends Mob {} // The Player
How I want to serzialize the map:
public void loadEntities(String path) {
ObjectMapper om = new ObjectMapper();
try {
runThread.mobs = om.readValue(new File(path + "/entities.json"), HashMap.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
But what do I need to do, in order for the HashMap to deserzialize the mob player in the JSON-file correctly and later serialize it back as a player into the HashMap, instead of a normal mob-object?
What my JSON could look like:
{
"0" : {
"posX" : 256,
"posY" : 128,
"rotation" : "links"
},
"2" : {
"posX" : 128,
"posY" : 256,
"rotation" : "rechts"
}
}
The 0 and the 2 are the IDs of the entities, but I can change the HashMap to a list if needed.
Full example of my mob:
import de.pki.GUI.Welt.Tiles.Tiles;
import java.awt.image.BufferedImage;
public abstract class Mob {
public float posX;
public float posY;
public float screenX;
public float screenY;
// Physik
public float forceX;
public float forceY;
public float gravity = 9.81F;
public float currentGravity = gravity * 2;
public float rotation; // Für Projektile, welche sich in der Luft drehen müssen
public String richtung; // Für normale Mobs, welche nur zwei Richtungen brauchen
public boolean vectorAble; // TODO
public float standardSpeed;
public float sprintSpeed;
public float currentSpeed;
public byte maxSpruenge = 2;
public byte spruengeGenutzt;
public float sprungKraft;
public float aktuelleSprungkraft = -5;
public float lastJump = System.currentTimeMillis();
// Hit box
public short startBoxX;
public short startBoxY;
public short endBoxX;
public short endBoxY;
public float opacity;
// Bilder
public BufferedImage idleLinks;
public BufferedImage idleRechts;
public BufferedImage[] links;
public BufferedImage[] rechts;
public BufferedImage currentImg;
public byte groesse = 1;
public byte imageCount = 0;
public double lastImageAnimation = System.currentTimeMillis();
// Methoden
// Physik
public void updateForces(CollisionChecker collisionChecker, int tickSpeed) {
if (forceX > 0) {
collisionChecker.checkRunning((int) (posX + startBoxX + endBoxX), forceX / tickSpeed, 1, this);
} else if (forceX < 0) {
collisionChecker.checkRunning((int) (posX + startBoxX - 4), (forceX * - 1) / tickSpeed, -1, this);
}
if (forceY > 0) {
collisionChecker.checkFalling((int) (posY + startBoxY + endBoxY), forceY / tickSpeed, 1, this);
} else if (forceY < 0) {
collisionChecker.checkFalling((int) (posY + startBoxY - 2), (forceY * - 1) / tickSpeed, -1, this);
}
}
public void gravity(double tickSpeed) {
forceY += (float) (currentGravity / tickSpeed);
}
public void jump() {
if (lastJump + 500 < System.currentTimeMillis()) {
if (spruengeGenutzt < maxSpruenge) {
forceY = aktuelleSprungkraft;
spruengeGenutzt++;
lastJump = System.currentTimeMillis();
}
}
}
// Für Objekte, welche Vektoren besitzen
public void changeVektor(Mob mob, Tiles tiles, double aufprallWinkel) {
if (mob.vectorAble) {}
// TODO: Noch hinzufügen
}
// Grafische Sachen
public void updateImages() {
switch (richtung) {
case "rechts" -> {
if (System.currentTimeMillis() - lastImageAnimation >= (double) 60 / rechts.length / 1000 + 150) {
if (imageCount + 1 >= rechts.length) {
imageCount = 0;
} else {
imageCount++;
}
currentImg = rechts[imageCount];
lastImageAnimation = System.currentTimeMillis();
}
}
case "links" -> {
if (System.currentTimeMillis() - lastImageAnimation >= (double) 60 / links.length / 1000 + 150) {
if (imageCount + 1 >= links.length) {
imageCount = 0;
} else {
imageCount++;
}
currentImg = links[imageCount];
lastImageAnimation = System.currentTimeMillis();
}
}
}
if (forceX == 0) {
if (richtung.equals("rechts")) {
currentImg = idleRechts;
} else if (richtung.equals("links")) {
currentImg = idleLinks;
}
}
}
}
And my player:
package de.pki.Entities.Spieler;
import de.pki.Entities.Mob;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Spieler extends Mob {
public Spieler() {
loadVariables();
loadImages();
hitBoxRechts();
}
private void loadImages() {
try {
links = new BufferedImage[2];
rechts = new BufferedImage[2];
idleLinks = ImageIO.read(new FileInputStream("src/Data/Images/Entities/Spieler/Idle/left_idle.png"));
idleRechts = ImageIO.read(new FileInputStream("src/Data/Images/Entities/Spieler/Idle/right_idle.png"));
links[0] = ImageIO.read(new FileInputStream("src/Data/Images/Entities/Spieler/WalkingAnimation/linksLauf1.png"));
links[1] = ImageIO.read(new FileInputStream("src/Data/Images/Entities/Spieler/WalkingAnimation/linksLauf2.png"));
rechts[0] = ImageIO.read(new FileInputStream("src/Data/Images/Entities/Spieler/WalkingAnimation/rechtsLauf1.png"));
rechts[1] = ImageIO.read(new FileInputStream("src/Data/Images/Entities/Spieler/WalkingAnimation/rechtsLauf2.png"));
currentImg = idleRechts;
} catch (IOException e) {
System.out.println(e);
}
}
private void loadVariables() {
sprungKraft = (float) (-9.81 * 1);
aktuelleSprungkraft = sprungKraft;
maxSpruenge = 2;
spruengeGenutzt = 0;
standardSpeed = 3;
sprintSpeed = 4.5F;
currentSpeed = standardSpeed;
richtung = "rechts";
groesse = 2;
hitBoxRechts();
}
public void hitBoxRechts() {
startBoxX = (short) (6 * groesse);
startBoxY = (short) (3 * groesse);
endBoxX = (short) (19 * groesse);
endBoxY = (short) (23 * groesse);
}
}```
(I'm currently in class, so it took me some time to edit the code)
Thanks for the help
To properly serialize polymorphic instances, you need to use the annotations JsonTypeInfo
and JsonSubTypes
. Basically, JsonTypeInfo
allows you to define an extra property when serializing an instance of the annotated class, while JsonSubTypes
defines the value of that extra property for each subclass. In your case, it would look something like this:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "class")
@JsonSubTypes({
@JsonSubTypes.Type(value = Player.class, name = "player")
})
public abstract class Mob {
//... implementation ...
}
public class Player extends Mob {
//... implementation ...
}
So, your Json would be something like:
{
"0": {
"posX": 1.0,
"posY": 2.0,
"rotation": "links",
"class": "player"
},
"2": {
"posX": 3.0,
"posY": 4.0,
"rotation": "rechts",
"class": "player"
}
}
Of course, JsonTypeInfo
and JsonSubTypes
can be used for multiple subclasses, and for each one of them you just need to define their corresponding @JsonSubTypes.Type
. For example, if there was an Enemy
class, it would be something like this:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "class")
@JsonSubTypes({
@JsonSubTypes.Type(value = Player.class, name = "player"),
@JsonSubTypes.Type(value = Enemy.class, name = "enemy")
})
public abstract class Mob {
//... implementation ...
}
public class Player extends Mob {
//... implementation ...
}
public class Enemy extends Mob {
//... implementation ...
}
Here is also an interesting article from Baeldung covering the topic.
Since you want to store your Player
instances in a generic data structure, like Map
or List
, you need to use a TypeReference
subclassing your data structure, so that Jackson knows how to properly deserialize the json.
ObjectMapper mapper = new ObjectMapper();
Map<Byte, Mob> map = mapper.readValue(json, new TypeReference<Map<Byte, Mob>>() {});
Here, is a link to OneCompiler with a demo of your code. As you can see from the output, even though it is used a Map<Byte, Mob>
to read the json, the instances within the Map
are Player
objects, which answers your question:
But what do I need to do, in order for the HashMap to deserzialize the mob player in the JSON-file correctly and later serialize it back as a player into the HashMap, instead of a normal mob-object?