Как правильно сериализовать/десериализовать объекты классов со сложной структурой в json
Скорее всего такой вопрос уже задавался, но всё, что я смог найти, немного но всё же не подходило.
Суть вопроса: мне в проекте нужно иметь возможность сохранять и читать из файла данные об объекте класса CollectionManager который хранит в себе HashSet объектов класса Dragon и ещё пару переменных. Собственно проблема состоит в том, что объекты класса Dragon тоже содержат в себе другие объекты уже классов DragonCave и Coordinates. Мне надо как-то сохранить информацию о том, что в collectionManager который я записываю/читаю лежит hashset драконов, у которых внутри ещё лежат их пещеры и их координаты.
Есть ещё один маленький подвопрос: у меня в менеджере коллекций и в самом драконе есть поля типа java.time.LocalDate, при попытке сериализовать их при помощи библиотеки Gson выбрасывается исключение "Unable to make field private static final long java.time.LocalDate.serialVersionUID accessible: module java.base does not "opens java.time" to unnamed module @6d86b085"
Спасибо за потраченное на меня время.
код классов и парсера: CollectionManager:
package Managers;
import Classes.Dragon;
import Exceptions.FieldNullException;
import Exceptions.IncorrectFieldValueException;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.StringJoiner;
public class CollectionManager {
private long currentId = 0;
private HashSet<Dragon> collection;
private final LocalDate initializationDate;
private String filePath;
public CollectionManager(HashSet<Dragon> collection, String filePath) throws IncorrectFieldValueException, FieldNullException {
this.filePath = filePath;
this.collection = collection;
initializationDate = getDate();
}
public void setFilePath(String filePath){
this.filePath = filePath;
}
public long getNewId(){
return currentId++;
}
public LocalDate getDate(){
return LocalDate.now();
}
public HashSet<Dragon> getCollection(){
return collection;
}
public void addElement(Dragon element){
collection.add(element);
}
public void removeElementById(Long id){
collection.removeIf(d -> d.getId().equals(id));
}
public void clear(){
collection.clear();
}
public LocalDate getInitializationDate() {
return initializationDate;
}
public String toString() {
StringJoiner stringJoiner = new StringJoiner("\n");
if (collection.size() > 0) {
collection.forEach((k) -> stringJoiner.add(k.toString()));
} else {
stringJoiner.add("The collection is empty");
}
return stringJoiner.toString();
}
}
Dragon:
package Classes;
import Managers.CollectionManager;
import Exceptions.FieldNullException;
import Exceptions.IncorrectFieldValueException;
import java.time.LocalDate;
import java.util.Objects;
public class Dragon implements Comparable<Dragon>{
private Long id; //Поле не может быть null, Значение поля должно быть больше 0, Значение этого поля должно быть уникальным, Значение этого поля должно генерироваться автоматически
private String name; //Поле не может быть null, Строка не может быть пустой
private Coordinates coordinates; //Поле не может быть null
private java.time.LocalDate creationDate; //Поле не может быть null, Значение этого поля должно генерироваться автоматически
private int age; //Значение поля должно быть больше 0
private float wingspan; //Значение поля должно быть больше 0
private Boolean speaking; //Поле не может быть null
private Color color; //Поле не может быть null
private DragonCave cave; //Поле не может быть null
public Dragon(CollectionManager collectionManager, String name, Coordinates coordinates, int age,
float wingspan, Boolean speaking, Color color, DragonCave cave)
throws FieldNullException, IncorrectFieldValueException {
if (collectionManager == null) {
throw new FieldNullException("CollectionManager");
} else if (name == null) {
throw new FieldNullException("name");
} else if (coordinates == null) {
throw new FieldNullException("coordinates");
} else if (speaking == null) {
throw new FieldNullException("speaking");
} else if (color == null) {
throw new FieldNullException("color");
} else if (cave == null) {
throw new FieldNullException("cave");
}
if (name.equals("")) {
throw new IncorrectFieldValueException("name", name, "not null and not \"\"");
} else if (age <= 0) {
throw new IncorrectFieldValueException("age", String.valueOf(age), "greater than 0");
} else if (wingspan <= 0) {
throw new IncorrectFieldValueException("wingspan", String.valueOf(wingspan), "greater than 0");
}
this.id = collectionManager.getNewId();
this.name = name;
this.coordinates = coordinates;
this.creationDate = collectionManager.getDate();
this.age = age;
this.wingspan = wingspan;
this.speaking = speaking;
this.color = color;
this.cave = cave;
}
// гетеры
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Coordinates getCoordinates() {
return coordinates;
}
public LocalDate getCreationDate() {
return creationDate;
}
public int getAge() {
return age;
}
public float getWingspan() {
return wingspan;
}
public Boolean getSpeaking() {
return speaking;
}
public Color getColor() {
return color;
}
public DragonCave getCave() {
return cave;
}
// сеттеры
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setCoordinates(Coordinates coordinates) {
this.coordinates = coordinates;
}
public void setCreationDate(LocalDate creationDate) {
this.creationDate = creationDate;
}
public void setAge(int age) {
this.age = age;
}
public void setWingspan(float wingspan) {
this.wingspan = wingspan;
}
public void setSpeaking(Boolean speaking) {
this.speaking = speaking;
}
public void setColor(Color color) {
this.color = color;
}
public void setCave(DragonCave cave) {
this.cave = cave;
}
public int compareTo(Dragon anotherDragon){
if (name.compareTo(anotherDragon.getName()) != 0) {
return name.compareTo(anotherDragon.getName());
}
else if (Integer.valueOf(age).compareTo(anotherDragon.getAge()) != 0){
return Integer.valueOf(age).compareTo(anotherDragon.getAge());
}
else{
return Float.valueOf(wingspan).compareTo(anotherDragon.getWingspan());
}
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Dragon dragon)) return false;
return age == dragon.age && Float.compare(dragon.wingspan, wingspan) == 0 && id.equals(dragon.id) &&
name.equals(dragon.name) && coordinates.equals(dragon.coordinates) &&
creationDate.equals(dragon.creationDate) && speaking.equals(dragon.speaking) &&
color == dragon.color && cave.equals(dragon.cave);
}
public int hashCode() {
return Objects.hash(id, name, coordinates, creationDate, age, wingspan, speaking, color, cave);
}
public String toString() {
return "Dragon{" +
"id=" + id +
", name='" + name + '\'' +
", coordinates=" + coordinates +
", creationDate=" + creationDate +
", age=" + age +
", wingspan=" + wingspan +
", speaking=" + speaking +
", color=" + color +
", cave=" + cave +
'}';
}
}
Coordinates:
package Classes;
import Exceptions.IncorrectFieldValueException;
public class Coordinates {
private long x; //Значение поля должно быть больше -846
private double y;
public Coordinates(long x, double y) throws IncorrectFieldValueException{
setX(x);
setY(y);
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public long getX() {
return x;
}
public void setX(long x) throws IncorrectFieldValueException {
if (x < -845) throw new IncorrectFieldValueException("x", String.valueOf(x), "greater than -846");
this.x = x;
}
@Override
public String toString() {
return "Coordinates{" +
"x=" + x +
", y=" + y +
'}';
}
}
DragonCave:
package Classes;
import java.util.Objects;
public class DragonCave {
private int depth;
public DragonCave(int depth){
this.depth = depth;
}
public int getDepth() {
return depth;
}
public void setDepth(int depth) {
this.depth = depth;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DragonCave)) return false;
DragonCave that = (DragonCave) o;
return depth == that.depth;
}
public int hashCode() {
return Objects.hash(depth);
}
public String toString() {
return "DragonCave{" +
"depth=" + depth +
'}';
}
}
Color:
package Classes;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
public enum Color {
BLUE("синий"),
YELLOW("жёлтый"),
WHITE("белый");
private final String colorName;
private Color(String colorName) {
this.colorName = colorName;
}
private final static Map<String, Color> colors = Arrays.stream(Color.values())
.collect(Collectors.toMap(k->k.colorName, v->v));
public static Color getColorByName(String colorName) {
return colors.get(colorName);
}
public String toString() {
return colorName;
}
}
Parser:
package Managers;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.HashSet;
import java.util.Scanner;
import Exceptions.FieldNullException;
import Exceptions.IncorrectFieldValueException;
import com.google.gson.Gson;
public class Parser {
private Parser() {
}
public static CollectionManager convertToJavaObject(File file) throws FieldNullException, IncorrectFieldValueException, FileNotFoundException {
Gson gson = new Gson();
// магическим образом получить строку(data) из всего этого
Scanner scan = new Scanner(file);
String data = scan.nextLine();
// это просто отвратительно, но другого способа не придумал
CollectionManager collectionManager = gson.fromJson(data, CollectionManager.class);
collectionManager.setFilePath(file.getPath());
return new CollectionManager(new HashSet<>(), "");
}
public static void convertToJSON(CollectionManager data){
Gson gson = new Gson();
String json = gson.toJson(data);
System.out.println(json);
}
}
Ответы (1 шт):
Решение я смог найти сам. В этом мне помогла вот эта статья. Пришлось написать отдельные сериализаторы и десириализаторы для каждого сложного объекта. Привожу их код (наверняка не солидный и переусложнённый, но точно работающий).
CaveSerializer
import Classes.DragonCave;
import com.google.gson.*;
import java.lang.reflect.Type;
public class CaveSerializer implements JsonSerializer<DragonCave>
{
@Override
public JsonElement serialize(DragonCave cave, Type typeOfSrc, JsonSerializationContext context)
{
JsonObject result = new JsonObject();
result.addProperty("depth", cave.getDepth());
return result;
}
}
CaveDeserializer
import Classes.DragonCave;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.lang.reflect.Type;
public class CaveDeserializer implements JsonDeserializer<DragonCave> {
public DragonCave deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
JsonObject jsonObject = json.getAsJsonObject();
return new DragonCave(jsonObject.get("depth").getAsInt());
}
}
CoordinatesSerializer
import Classes.Coordinates;
import com.google.gson.*;
import java.lang.reflect.Type;
public class CoordinatesSerializer implements JsonSerializer<Coordinates>
{
@Override
public JsonElement serialize(Coordinates cords, Type typeOfSrc, JsonSerializationContext context)
{
JsonObject result = new JsonObject();
result.addProperty("x", cords.getX());
result.addProperty("y", cords.getY());
return result;
}
}
CoordinatesDeserializer
package JSONstaff;
import Classes.Coordinates;
import Exceptions.IncorrectFieldValueException;
import com.google.gson.*;
import java.lang.reflect.Type;
public class CoordinatesDeserializer implements JsonDeserializer<Coordinates> {
public Coordinates deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
JsonObject jsonObject = json.getAsJsonObject();
try {
return new Coordinates(jsonObject.get("x").getAsLong(), jsonObject.get("y").getAsDouble());
} catch (IncorrectFieldValueException e) {
e.printStackTrace();
return null;
}
}
}
DragonSerializer
package JSONstaff;
import Classes.Dragon;
import com.google.gson.*;
import java.lang.reflect.Type;
public class DragonSerializer implements JsonSerializer<Dragon>
{
public JsonElement serialize(Dragon src, Type typeOfSrc, JsonSerializationContext context)
{
JsonObject result = new JsonObject();
result.addProperty("id", src.getId());
result.addProperty("name", src.getName());
result.add("coordinates", context.serialize(src.getCoordinates()));
result.addProperty("creationDate", src.getCreationDate().toString());
result.addProperty("age", src.getAge());
result.addProperty("wingspan", src.getWingspan());
result.addProperty("speaking", src.getSpeaking());
result.addProperty("color", src.getColor().toString());
result.add("cave", context.serialize(src.getCave()));
return result;
}
}
DragonDeserializer
package JSONstaff;
import Classes.Color;
import Classes.Coordinates;
import Classes.Dragon;
import Classes.DragonCave;
import Exceptions.FieldNullException;
import Exceptions.IncorrectFieldValueException;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.lang.reflect.Type;
public class DragonDeserializer implements JsonDeserializer<Dragon> {
public Dragon deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context){
JsonObject jsonObject = json.getAsJsonObject();
Dragon dragon = null;
try {
dragon = new Dragon(jsonObject.get("name").getAsString(),
context.deserialize(jsonObject.get("coordinates"), Coordinates.class),
jsonObject.get("age").getAsInt(),
jsonObject.get("wingspan").getAsFloat(),
jsonObject.get("speaking").getAsBoolean(),
Color.getColorByName(jsonObject.get("color").getAsString()),
context.deserialize(jsonObject.get("cave"), DragonCave.class));
} catch (FieldNullException | IncorrectFieldValueException e) {
e.printStackTrace();
}
assert dragon != null;
dragon.setId(jsonObject.get("id").getAsLong());
return dragon;
}
}
CollectionSerializer
package JSONstaff;
import Classes.Dragon;
import Managers.CollectionManager;
import com.google.gson.*;
import java.lang.reflect.Type;
public class CollectionSerializer implements JsonSerializer<CollectionManager> {
public JsonElement serialize(CollectionManager collectionManager, Type typeOfSrc, JsonSerializationContext context){
JsonObject result = new JsonObject();
JsonArray col = new JsonArray();
for(Dragon dragon : collectionManager.getCollection()) {
col.add(context.serialize(dragon));
}
result.addProperty("currentId", collectionManager.getId());
result.add("collection", col);
//result.addProperty("filePath", collectionManager.getFilePath());
//не думаю, что файл должен хранить в себе путь к себе же
result.addProperty("initializationDate", collectionManager.getInitializationDate().toString());
return result;
}
}
CollectionDeserializer
package JSONstaff;
import Classes.Dragon;
import Exceptions.FieldNullException;
import Exceptions.IncorrectFieldValueException;
import Managers.CollectionManager;
import com.google.gson.*;
import java.lang.reflect.Type;
import java.util.HashSet;
public class CollectionDeserializer implements JsonDeserializer<CollectionManager> {
public CollectionManager deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
JsonObject jsonObject = json.getAsJsonObject();
HashSet<Dragon> collection = new HashSet();
for(JsonElement dragon : jsonObject.get("collection").getAsJsonArray()) {
collection.add(context.deserialize(dragon, Dragon.class));
}
try {
return new CollectionManager(collection, jsonObject.get("filePath").getAsString());
} catch (IncorrectFieldValueException | FieldNullException e) {
e.printStackTrace();
return null;
}
}
}
Переделанный Parser
package Managers;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.*;
import Classes.Coordinates;
import Classes.Dragon;
import Classes.DragonCave;
import JSONstaff.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class Parser {
private Parser() {
}
public static CollectionManager convertToJavaObject(File file) throws FileNotFoundException{
Scanner scan = new Scanner(file);
StringJoiner data = new StringJoiner("");
while (scan.hasNextLine()){
data.add(scan.nextLine());
}
Gson gson = new GsonBuilder()
.registerTypeAdapter(CollectionManager.class, new CollectionDeserializer())
.registerTypeAdapter(Dragon.class, new DragonDeserializer())
.registerTypeAdapter(Coordinates.class, new CoordinatesDeserializer())
.registerTypeAdapter(DragonCave.class, new CaveDeserializer())
.create();
return gson.fromJson(data.toString(), CollectionManager.class);
}
public static void convertToJSON(CollectionManager data){
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(CollectionManager.class, new CollectionSerializer())
.registerTypeAdapter(Dragon.class, new DragonSerializer())
.registerTypeAdapter(Coordinates.class, new CoordinatesSerializer())
.registerTypeAdapter(DragonCave.class, new CaveSerializer())
.create();
}
}