Ссылки между классами не сохраняются при загрузке из JSON - PullRequest
1 голос
/ 27 октября 2019

У меня есть объект World, который имеет некоторые ссылки на двумерный массив Tiles и другой двумерный массив PathNodes. И Tile, и PathNode являются классами, которые имеют значения и ссылку между ними. (Плитка 0,0 ссылается на узел 0,0 и т. Д.).

Я пытаюсь сохранить объект World в JSON, сохраняя ссылки на объекты. Моя проблема в том, что когда я позже загружаю мир из JSON, тайл теряет ссылку на узел и наоборот.

World Class:

public class World
{
    public Tile[,] tiles;
    public PathNode[,] pathNodes;

    public World(int width, int height)
    {
        tiles = new Tile[width, height];
        pathNodes = new PathNode[width, height];

        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                tiles[x, y] = new Tile(this, x, y);
                pathNodes[x, y] = new PathNode(x, y);

                tiles[x, y].pathNode = pathNodes[x, y];
                pathNodes[x, y].tile = tiles[x, y];
            }
        }
    }
}

Классы плиток и PathNode:

public class Tile
{
    private World world;

    private int x;
    public int X { get { return x; } protected set { } }

    private int y;
    public int Y { get { return y; } protected set { } }

    public Tile (World world, int x, int y)
    {
        this.world = world;
        this.x = x;
        this.y = y;
    }

    [JsonProperty]
    public PathNode pathNode;
}

public class PathNode
{
    private int x;
    public int X { get { return x; } protected set { } }

    private int y;
    public int Y { get { return y; } protected set { } }

    public PathNode(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    [JsonProperty]
    public Tile tile;
}

Сценарий сохранения:

public class WorldSave
{
    public void SaveWorld(World worldToSave)
    {
        SaveSystem.Init();
        string json = JsonConvert.SerializeObject(worldToSave, Formatting.Indented, new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.Auto,
            PreserveReferencesHandling = PreserveReferencesHandling.Objects
        });

        File.WriteAllText(SaveSystem.SAVE_FOLDER + "/Save.json", json);
    }

    public World LoadWorld()
    {
        World saveWorld = null;
        if (File.Exists(SaveSystem.SAVE_FOLDER + "/Save.json"))
        {
            string saveString = File.ReadAllText(SaveSystem.SAVE_FOLDER + "/Save.json");
            saveWorld = JsonConvert.DeserializeObject<World>(saveString, new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto,
                ReferenceLoopHandling = ReferenceLoopHandling.Serialize

            });
            Debug.Log("Save world es: " + saveWorld);
        }
        return saveWorld;
    }
}

JSON:

{
  "$id": "1",
  "tiles": [
    [
      {
        "$id": "2",
        "pathNode": {
          "$id": "3",
          "tile": {
            "$ref": "2"
          },
          "X": 0,
          "Y": 0
        },
        "X": 0,
        "Y": 0
      }
    ]
  ],
  "pathNodes": [
    [
      {
        "$ref": "3"
      }
    ]
  ]
}

Я не думаю, что все в порядке, что плитка имеет все свойства узла, хранящиеся в нейвместо того, чтобы иметь их в разделе pathNodes. Я не понимаю, почему это происходит.

Я использую JSON .Net для Unity

1 Ответ

1 голос
/ 28 октября 2019

Формат JSON, который вы видите при использовании PreserveReferencesHandling.Objects, является нормальным и ожидаемым. В первый раз, когда сериализатор обнаруживает ссылку на объект, он полностью сериализует этот объект и присваивает ему значение $id, которое записывается в JSON;все последующие ссылки на тот же объект заменяются на $ref, который ссылается на исходный $id. Таким образом, здесь происходит то, что сериализатор встречает узлы пути посредством ссылок на них в каждой ячейке, пока он итерирует по массиву tiles. Когда он позже выполняет итерацию по массиву pathNodes, все эти объекты уже встречались ранее, поэтому сериализировать нечего, кроме значения $ref. Это все работает как задумано и не является причиной проблемы здесь.

Основная проблема заключается в том, что у ваших объектов отсутствуют конструкторы по умолчанию (без параметров). PreserveReferencesHandling плохо работает с параметризованными конструкторами, поскольку необходимая информация для передачи конструктору может не загружаться из JSON к тому времени, когда сериализатору необходимо создать объект. Это проблема курицы и яйца. Поэтому должен быть способ создать пустой объект, а затем заполнить соответствующую информацию позже.

Вы можете исправить свои классы, выполнив следующие действия:

  1. Добавьте частные конструкторы по умолчанию для Json.Net для использования в ваших World, Tile и PathNode классах, например:

    [JsonConstructor]
    private Tile() { }
    
  2. В ваших Tile иКлассы PathNode предоставляют Json.Net способ установки X и Y, поскольку единственный способ сделать это сейчас - через открытый конструктор. Вы можете сделать это, пометив эти свойства [JsonProperty] и введя код для методов set:

    private int x;
    [JsonProperty]
    public int X { get { return x; } protected set { x = value; } }
    
  3. Если вы хотите указать ссылку world в вашем Tile класс, который будет повторно заполнен при десериализации, эта ссылка должна быть в JSON (в настоящее время ее нет) и должна быть доступна для Json.Net. Это можно исправить, пометив частное поле world [JsonProperty] следующим образом:

    [JsonProperty]
    private World world;
    

С этими изменениями ссылки должны быть правильно восстановлены при десериализации.

Вот демоверсия рабочей поездки в оба конца: https://dotnetfiddle.net/zrQsQ9

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...