Java游戏开发:Jackson序列化HashMap多子类对象的问题
问题:Jackson反序列化HashMap<Byte, Mob>时丢失子类类型
我用Java开发游戏,使用Jackson存储实体数据时遇到问题:需要对存储父类Mob子类(比如Spieler)的HashMap<Byte, Mob>进行序列化与反序列化,但反序列化后JSON中的Spieler对象会被转换为普通Mob对象,无法保留原子类类型。
相关代码
实体存储与类定义
public HashMap<Byte, Mob> mobs = new HashMap<>(); // Byte作为键足够用,后续可按需修改 public class Spieler extends Mob {} // 玩家类,继承Mob
反序列化代码
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); } }
JSON示例
{ "0" : { "posX" : 256, "posY" : 128, "rotation" : "links" }, "2" : { "posX" : 128, "posY" : 256, "rotation" : "rechts" } }
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; // 物理相关 public float forceX; public float forceY; public float gravity = 9.81F; public float currentGravity = gravity * 2; public float rotation; // 用于需要在空中旋转的抛射物 public String richtung; // 用于仅需两个方向的普通怪物 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(); // 碰撞箱 public short startBoxX; public short startBoxY; public short endBoxX; public short endBoxY; public float opacity; // 图像资源 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(); // 方法 // 物理更新 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(); } } } // 处理带向量的对象 public void changeVektor(Mob mob, Tiles tiles, double aufprallWinkel) { if (mob.vectorAble) {} // TODO: 待实现 } // 图像更新 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; } } } }
Spieler类完整代码
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); } }
解决方案
要让Jackson正确识别并反序列化子类,需要启用类型信息的序列化与反序列化,具体操作如下:
1. 给父类Mob添加Jackson类型注解
在抽象类Mob上添加@JsonTypeInfo和@JsonSubTypes注解,告诉Jackson如何识别子类:
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import de.pki.GUI.Welt.Tiles.Tiles; import java.awt.image.BufferedImage; @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type" ) @JsonSubTypes({ @JsonSubTypes.Type(value = Spieler.class, name = "player") }) public abstract class Mob { // 原类内容不变 }
@JsonTypeInfo:指定类型信息的存储方式——用NAME作为类型标识,在JSON中添加type字段存储子类名称@JsonSubTypes:列出所有需要识别的子类,比如Spieler对应名称player
2. 修改反序列化代码,使用泛型类型
之前用HashMap.class会丢失泛型信息,需要用TypeReference指定具体的泛型类型:
import com.fasterxml.jackson.core.type.TypeReference; import java.io.File; import java.io.IOException; public void loadEntities(String path) { ObjectMapper om = new ObjectMapper(); try { // 使用TypeReference明确指定HashMap的泛型类型 runThread.mobs = om.readValue( new File(path + "/entities.json"), new TypeReference<HashMap<Byte, Mob>>() {} ); } catch (IOException e) { throw new RuntimeException(e); } }
3. 更新JSON数据,添加type字段
序列化时Jackson会自动给子类对象添加type字段,反序列化需要这个字段识别类型,修改后的JSON如下:
{ "0" : { "type": "player", "posX" : 256, "posY" : 128, "rotation" : "links" }, "2" : { "type": "player", "posX" : 128, "posY" : 256, "rotation" : "rechts" } }
额外说明
- 后续新增
Mob子类时,只需在@JsonSubTypes中添加对应的@JsonSubTypes.Type条目即可 - 若不想手动修改现有JSON,可先运行一次序列化代码,Jackson会自动生成包含
type字段的JSON,再用该JSON作为后续反序列化的数据源
内容的提问来源于stack exchange,提问作者NoNameIdea
相关产品推荐
相关产品推荐

