Хэш-карта сериализации Джексона (де-) с различными типами объектов

В настоящее время я программирую игру на 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
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
58
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

См. 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, а не обычный моб-объект?

Другие вопросы по теме