You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 21:14:59