2

I've been working on a music game and decided to add conversion of other games' levels. One of the games I decided to convert from uses JSON to store it's levels and so I'm using Newtonsoft.Json for deserializing level data. Level have can 2 object types that are stored in a single array/list with one shared property and one individual property. Keeping that in mind I made level class with it's properties, one base and two inherited classes:

class Chart
{
   //Some unimportant properties
   //...

   public Note[] note;

   class Note
   {
      public int[] beat;
   }

   class NoteSingle : Note
   {
      public int x;
   }

   class NoteRain : Note
   {
      public int[] endbeat;
   }
}

However, when I try deserialize level, note only contains base objects. I tried creating JsonSerializerSettings with TypeNameHandling set to All and passing it to deserialization method, but it didn't worked, note still only have base classes in it.

Basically I need to load level from the file, deserialize it as Chart and make each of the notes in note be one of the types inherited from Note depending on json data. Like if note has x field then load it as NoteSingle and if it has endbeat field then load it as NoteRain.

Repres

class Note
{
    public int[] beat;
}

class NoteSingle : Note
{
    public int x;
}

class NoteRain : Note
{
    public int[] endbeat;
}

class Chart
{
    //Some unimportant properties
    public Note[] note;
    //Some unimportant properties
}

public static void Convert(string path)
{
    string rawData = File.ReadAllText(path);
    JsonSerializerSettings setts = new JsonSerializerSettings()
    {
        TypeNameHandling = TypeNameHandling.All
    };
    Chart ch = JsonConvert.DeserializeObject<Chart>(rawData, setts);

    //Level converter code
}

Example data I'm trying to load: https://pastebin.com/zgnRsgWZ

What am I doing wrong?

4
  • 1
    Please give a minimal reproducible example and more explination on what you want to achive. Yes, if you deserialize the type as base type, it will only know the base type properties. Same for any inheritance: if you access note from C#, you can also not access x and such. Commented May 17, 2022 at 8:21
  • Basically I need to load all notes from the file into array and make each of them be one of the inherited types depending on json data. Like if note has x field then load it as NoteSingle and if it has endbeat field then load it as NoteRain. As for repres give me some time, I'll edit this comment Commented May 17, 2022 at 10:54
  • You really need to put all the relevant data in the question. Not in the comments like you are doing now. Stack overflow is not a forum. Your situation is very specific. There is no general solution. Commented May 17, 2022 at 11:21
  • Right, I'll edit the post in a moment Commented May 17, 2022 at 11:26

1 Answer 1

2

I try with this code:

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All
};

var chart = new Chart();
chart.note = new Note[]
{
        new NoteSingle { x = 37 },
        new NoteRain { endbeat = new[] { 9 } }
};

var json = JsonConvert.SerializeObject(chart, settings);
var chart2 = JsonConvert.DeserializeObject<Chart>(json, settings);

And it's working. json has this value:

{
   "$type":"Test.Chart, SoApp",
   "note":{
      "$type":"Test.Chart+Note[], SoApp",
      "$values":[
         {
            "$type":"Test.Chart+NoteSingle, SoApp",
            "x":37,
            "beat":null
         },
         {
            "$type":"Test.Chart+NoteRain, SoApp",
            "endbeat":{
               "$type":"System.Int32[], mscorlib",
               "$values":[
                  9
               ]
            },
            "beat":null
         }
      ]
   }
}

And chart2 has 2 notes of NoteSingle and NoteRain types. Maybe you aren't using TypeNameHandling.All in Serialize. You need to use both on Serialize and Deserialize.

UPDATE

If you haven't control of the generated JSON, you can use a Converter to deserialize it:

public class YourJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var chart = new Chart();
        var notes = new List<Note>();

        string name = null;
        NoteRain noteRain = null;

        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.PropertyName:
                    name = reader.Value.ToString();
                    break;
                    
                case JsonToken.Integer:
                    if (name == "x")
                    {
                        var note = new NoteSingle { x = Convert.ToInt32(reader.Value) };
                        notes.Add(note);
                    }
                    else if (name == "endbeat")
                    {
                        if (noteRain == null)
                        {
                            noteRain = new NoteRain { endbeat = new[] { Convert.ToInt32(reader.Value) } };
                            notes.Add(noteRain);
                        }
                        else
                        {
                            var array = noteRain.endbeat;
                            noteRain.endbeat = new int[noteRain.endbeat.Length + 1];
                                
                            for (int i = 0; i < array.Length; i++)
                            {
                                noteRain.endbeat[i] = array[i];
                            }

                            noteRain.endbeat[noteRain.endbeat.Length - 1] = Convert.ToInt32(reader.Value);
                        }
                    }

                    break;
            }
        }

        chart.note = notes.ToArray();
        return chart;
    }

    public override bool CanWrite => false;

    public override bool CanConvert(Type objectType)
    {
        return true;
    }
}

This is a simple example, you must tune it but I think it's easy to do. In the property name I get the name of the property and I use later to create the correct type. If I process a x property I know that is a NoteSingle and I create it and add to notes list. If, for example, you get a property name like beat and you don't know yet the type of the Note class, use a temporary variable to store and fill and later, when you read a property that you know is from a concrete class, create the Note instance and fill with this variable. And after that, If you read more data of this class, continue filling your instance.

Usage:

var settings = new JsonSerializerSettings();
settings.Converters.Add(new YourJsonConverter());
var chart = JsonConvert.DeserializeObject<Chart>(json, settings);
Sign up to request clarification or add additional context in comments.

1 Comment

No, I'm not trying to serialize it, I only need to deserialize data. Here's an example of level I need to load: pastebin.com/zgnRsgWZ

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.