В настоящее время я программирую игру на Java, и у меня возникла проблема с сохранением данных сущностей в Джексоне. Мне нужно (де-) сериализовать HashMap с различными подклассами суперкласса. Например. У меня есть класс Mob, и каждая сущность, например Player, является подклассом 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
Как я хочу серциализировать карту:
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);
}
}
Но что мне нужно сделать, чтобы HashMap корректно десериализовал игрока моба в JSON-файл и позже сериализовал его обратно как игрока в HashMap, а не обычный моб-объект?
Как может выглядеть мой JSON:
{
"0" : {
"posX" : 256,
"posY" : 128,
"rotation" : "links"
},
"2" : {
"posX" : 128,
"posY" : 256,
"rotation" : "rechts"
}
}
0 и 2 — это идентификаторы объектов, но при необходимости я могу изменить HashMap на список.
Полный пример моего моба:
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;
}
}
}
}
И мой плеер:
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




См. https://www.baeldung.com/java-deserialize-generic-type-with-jackson
Ты мог бы сделать
TypeReference typeRef = new TypeReference<Map<Byte, Mob>>() {};
runThread.mobs = om.readValue(new File(path + "/entities.json"), typeRef);
Чтобы правильно сериализовать полиморфные экземпляры, вам необходимо использовать аннотации JsonTypeInfo и JsonSubTypes. По сути, JsonTypeInfo позволяет вам определить дополнительное свойство при сериализации экземпляра аннотированного класса, а JsonSubTypes определяет значение этого дополнительного свойства для каждого подкласса. В вашем случае это будет выглядеть примерно так:
@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 ...
}
Итак, ваш Json будет выглядеть примерно так:
{
"0": {
"posX": 1.0,
"posY": 2.0,
"rotation": "links",
"class": "player"
},
"2": {
"posX": 3.0,
"posY": 4.0,
"rotation": "rechts",
"class": "player"
}
}
Конечно, JsonTypeInfo и JsonSubTypes можно использовать для нескольких подклассов, и для каждого из них вам просто нужно определить соответствующий @JsonSubTypes.Type. Например, если бы существовал класс Enemy, это было бы примерно так:
@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 ...
}
Вот также интересная статья от Baeldung, освещающая эту тему.
Поскольку вы хотите хранить экземпляры Player в общей структуре данных, например Map или List, вам необходимо использовать подкласс TypeReference вашей структуры данных, чтобы Джексон знал, как правильно десериализовать json.
ObjectMapper mapper = new ObjectMapper();
Map<Byte, Mob> map = mapper.readValue(json, new TypeReference<Map<Byte, Mob>>() {});
Вот ссылка на OneCompiler с демо-версией вашего кода. Как вы можете видеть из вывода, хотя для чтения json используется Map<Byte, Mob>, экземпляры внутри Map являются объектами Player, что отвечает на ваш вопрос:
Но что мне нужно сделать, чтобы HashMap корректно десериализовал игрока моба в JSON-файл и позже сериализовал его обратно как игрока в HashMap, а не обычный моб-объект?