Есть ли способ сериализовать указанные MonoBehaviours в JSON?

Допустим, у меня есть объект с именем ObjectA, который содержит два объекта: ObjectsB и ObjectC.

[System.Serializable]
public ClassA
{
    public ClassB classB;
    public ClassC classC;

    public ClassA()
    {
        classB = new ClassB();
        classC = new ClassC();
    }
}

[System.Serializable]
public ClassB
{
    //Some fields.
}

[System.Serializable]
public ClassC
{
    //Some fields.
}

Если я сериализую ObjectA в JSON, ObjectB и ObjectC не будут сериализованы. Вот что у меня получилось после сериализации ObjectA в JSON:
{"_instanceB":{"instanceID":10480},"_instanceC":{"instanceID":10230}}
Мне нужно сериализовать все эти объекты в файл и сохранить его локально на жестком диске, чтобы иметь возможность восстановить их состояние позже. Как мне это сделать?
Должен ли я извлекать ClassB и ClassC из ClassA, а затем сериализовать и сохранять их по отдельности? Что-то вроде этого:

public void Save()
{
    //Get instances of ClassB and ClassC.
    ClassB classB = classA.GetClassB;
    ClassC classC = classA.GetClassC;

    //Generate different JSON for each class.
    string classA_JSON = JsonUtility.ToJson(classA);
    string classB_JSON = JsonUtility.ToJson(classB);
    string classC_JSON = JsonUtility.ToJson(classC);

    //Paths for saving locally.
    string pathForClassA = Path.Combine("C:\\", "classA.json");
    string pathForClassB = Path.Combine("C:\\", "classB.json");
    string pathForClassC = Path.Combine("C:\\", "classC.json");

    File.WriteAllText(pathForClassA, classA_JSON);
    File.WriteAllText(pathForClassB, classB_JSON);
    File.WriteAllText(pathForClassC, classC_JSON);
}

Выглядит некрасиво, и он будет генерировать новый файл JSON для каждого вложенного класса. Могу ли я как-то сериализовать ClassA, включая его вложенные классы, в один файл JSON?

P.S. Это проект Unity, и ClassA, ClassB и ClassC происходят от MonoBehaviour. Поскольку BinaryFormatter не поддерживает MonoBehaviour, я не могу его использовать. Единственное, что мне осталось, это сериализовать его в JSON.

Можете ли вы показать нам код для функции JsonUtility.ToJson?

Pankaj Kapare 12.06.2019 22:24

@PankajKapare JsonUtility документация доступна здесь

Ruzihm 12.06.2019 22:26

@Ruzihm Да, я знаю об этих ограничениях. ClassB и ClassC имеют простые поля типа float, которые помечены атрибутом SerializeField.

blablaalb 12.06.2019 22:45

Поскольку ClassB и ClassC не являются MonoBehaviours, вы решили сделать их structs? Я предполагаю, что в противном случае вы могли бы получить ссылки на класс как сериализованное значение вместо фактических значений... хотя не уверен

derHugo 12.06.2019 22:49

@derHugo Отображает ли Unity поля структуры в инспекторе? Мне нужно иметь возможность редактировать их.

blablaalb 12.06.2019 22:55

он должен ... единственная разница между class и struct заключается в том, что class является ссылочным типом, а struct типом значения

derHugo 12.06.2019 22:59

Черт, я забыл, что ClassB и ClassC также происходят от MonoBehaviour, они не были MonoBehaviours с самого начала.. но позже мне пришлось сделать их, чтобы можно было ссылаться на них из другого игрового объекта.

blablaalb 12.06.2019 23:00
so could reference to them from another game object что вы имеете в виду? Это компоненты, прикрепленные к GameObjects?
derHugo 12.06.2019 23:02

С самого начала это были простые классы С#. но позже мне пришлось сделать их MonoBehaviours и добавить как компоненты, чтобы можно было ссылаться на них из другого игрового объекта.

blablaalb 12.06.2019 23:06

Должны ли они быть компонентами, на которые можно ссылаться из другого игрового объекта? Можете ли вы иметь компонент в GameObject, к которому вы бы их прикрепили, вместо того, чтобы иметь геттер, который их возвращает? Тогда это могли быть обычные занятия. И для ваших целей редактирования инспектора используйте таможенный инспектор для редактирования ClassB/ClassC.

Ruzihm 12.06.2019 23:14

Это именно то, о чем я сейчас думал: D Я попробую и посмотрю, сработает ли это.

blablaalb 12.06.2019 23:19

@blablaalb см. этот ответ для получения дополнительной информации

Ruzihm 12.06.2019 23:22

Спасибо @Ruzihm и @derHugo за помощь. Я попытаюсь реорганизовать свой код и сделать ClassB и ClassC чистые классы C# и получить для них геттерные свойства из ClassA.

blablaalb 12.06.2019 23:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
13
1 015
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Can I somehow serialize ClassA including its nested classes into one JSON file?

Да, вы могли бы, но это потребует некоторой работы:

Вы можете иметь [Serializable] классы представления для ClassB и ClassC и использовать ISerializationCallbackReceiver интерфейс для их заполнения и использования в ClassA

Что-то вроде, например.

public class ClassB : MonoBehaviour
{
    [SerializeField] private float example1;
    [SerializeField] private string example2;
    // etc.

    public void SetUp(SerializableClassB values)
    {
        // set all values
        this.example1 = values.example1;
        this.example2 = values.example2;
        // etc.
    }

    public SerializableClassB GetSerializable()
    {
        var output = new SerializableClassB();

        output.example1 = this.example1;
        output.example2 = this.example2;
        // etc.

        return output;
    }
}

[Serializable]
public class SerializableClassB
{
    public float example1;
    public string example2;
    // etc
}

И то же самое для ClassC

public class ClassC : MonoBehaviour
{
    [SerializeField] private float example3;
    [SerializeField] private string example4;
    // etc.

    public void SetUp(SerializableClassC values)
    {
        // set all values
        example3 = values.example3;
        example4 = values.example4;
        // etc.
    }

    public SerializableClassC GetSerializable()
    {
        var output = new SerializableClassC();

        output.example3 = example3;
        output.example4 = example4;
        // etc.

        return output;
    }
}

[Serializable]
public class SerializableClassC
{
    public float example3;
    public string example4;
    // etc
}

Тогда в ClassA вы можете сделать

public class ClassA : MonoBehaviour, ISerializationCallbackReceiver
{
    public ClassB _classB;
    public ClassC _classC;

    [SerializeField] private SerializableClassB _serializableClassB;
    [SerializeField] private SerializableClassC _serializeableClassC;


    public void OnBeforeSerialize()
    {
        // before writing to a Json get the information from the MonoBehaviours into the normal classes
        if (_classB) _serializableClassB = _classB.GetSerializable();
        if (_classC) _serializeableClassC = _classC.GetSerializable();

    }

    public void OnAfterDeserialize()
    {
        // after deserializing write the infromation from the normal classes into the MonoBehaviours
        if (_classB) _classB.SetUp(_serializableClassB);
        if (_classC) _classC.SetUp(_serializeableClassC);
    }
}

Второе огромное преимущество (побочный эффект) заключается в том, что теперь вы дополнительно можете также контролировать значения _classB и _classC непосредственно в экземпляре ClassA. Таким образом, вы можете изменять значения MonoBehaviour в централизованном классе менеджера.

После сериализации в json с использованием

private void Start()
{
    File.WriteAllText(Path.Combine(Application.streamingAssetsPath, "Test.txt"), JsonUtility.ToJson(this));
    AssetDatabase.Refresh();
}

теперь вы получаете

{
    "_classB":{"instanceID":-6766},"_classC":{"instanceID":-6826},
    "_serializableClassB": {
            "example1":23.0,
            "example2":"54ththfghg"
    },
    "_serializeableClassC": {
            "example3":67.0,
            "example4":"567gffhgfhgf"
    }
}

Чем для примера я изменил его на

{
    "_classB":{"instanceID":-6766},"_classC":{"instanceID":-6826},
    "_serializableClassB": {
            "example1":47,
            "example2":"Hello"
    },
    "_serializeableClassC": {
            "example3":32.123,
            "example4":"World!"
    }
}

и десериализован при запуске из json с использованием

private void Start()
{
    JsonUtility.FromJsonOverwrite(File.ReadAllText(Path.Combine(Application.streamingAssetsPath, "Test.txt")), this);
}

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