0

I can't parse JSON in Unity. Here is the JSON file:

{
  "play_classic": [
    { "english": "PLAY CLASSIC" },
    { "spanish": "JUEGO CLÁSICO" },
    { "portuguese": "JUEGO CLÁSICO" },
    { "russian": "IGRAT' V KLASSICHESKUYU" }
  ]
}

the structure and code looks like this:

[System.Serializable]
    class Root
    {
        public List<PlayClassicData> play_classic { get; set; }  
    }



[System.Serializable]
    class PlayClassicData
    {

        public string english { get; set; }

        public string spanish { get; set; }

        public string portuguese { get; set; }

        public string russian { get; set; }
    }

the implementation looks like this:

ParseJson class:

 public void Deserialize()
        {
string path = Application.dataPath + "path...";
        
        using (StreamReader r = new StreamReader(path))
        {
            string json = r.ReadToEnd();
            Root classic = JsonUtility.FromJson<Root>(json);
            //Root classic = JsonConvert.DeserializeObject<Root>(json);
            Debug.Log("english:" + classic.play_classic[0].english);
            Debug.Log("spanish:" + classic.play_classic[0].spanish);
       }
}

The code throws an error:

NullReferenceException: Object reference not set to an instance of an object
Assets.Scripts.Infastructure.PARSER.ParseJson.Deserialize () (at Assets/Scripts/Infastructure/PARSER/ParseJson.cs:23)
Anonymous
  • 738
  • 4
  • 14
  • 36
  • i didnt add whole code. – Nikola Jokovic Apr 20 '21 at 12:38
  • You might btw want to not use the built-in `JsonUtility` here but rather use e.g. `Newtonsoft .NET JSON` -> it supports [(de)serialization of **Dictionary**](https://www.newtonsoft.com/json/help/html/DeserializeDictionary.htm) so you could actually have `public Dictionary play_classic = new Dictionary();` and could easily extend it dynamically in your JSON file ... – derHugo Apr 21 '21 at 06:40
  • Also JsonUtility doesn't support properties .. you want to use fields by removing all the `{get; set}` – derHugo Apr 21 '21 at 06:50

3 Answers3

2

Your Json does not match your data

public PlayClassicData[] _playClassicData { get; set; } 

Should be:

public List<PlayClassic> play_classic { get; set; }

so classic._playClassicData is probebly null

Edit:

As derHugo pointed out:

Unity's built-in JsonUtility doesn't support properties.. you want to use fields by removing all the {get; set;}

Should be:

public List<PlayClassic> play_classic;
Connor Stoop
  • 1,574
  • 1
  • 12
  • 26
2

There are two problems here.

  1. Wrong Root. It should bepublic List<PlayClassicData> play_classic { get; set; }
  2. Indexing. classic._playClassicData[0].spanish is empty. It's the 2nd element that has the spanish value: classic._playClassicData[1].spanish);

Fixed Root:

class Root
{
   public List<PlayClassicData> play_classic { get; set; } 
}

Demo:

var json = File.ReadAllText("example1.json");
var o = JsonSerializer.Deserialize<Root>(json);
Console.WriteLine(o.play_classic[0].english);
Console.WriteLine(o.play_classic[1].spanish);

This prints:

PLAY CLASSIC
JUEGO CLÁSICO

I've used System.Json.Text.JsonSerializer but with JsonConvert.DeserializeObject<Root>(json); the result is the same.

tymtam
  • 31,798
  • 8
  • 86
  • 126
  • 1
    i changed still same error – Nikola Jokovic Apr 20 '21 at 12:49
  • 1
    a. Does `json` have the expected text? b. `JsonUtility.FromJson` that's where your null exception is. Use `JsonConvert.DeserializeObject(json)`. – tymtam Apr 20 '21 at 12:51
  • 1
    it is only works with JsonConvert.DeserializeObject...this is unity – Nikola Jokovic Apr 20 '21 at 12:57
  • Note that `System.Json.Text.JsonSerializer` is not available in Unity which uses `.Net Framework 4.x`, Unity's built-in JsonUtility and in general the serializer doesn't support properties. You want to convert them to fields by removing the `{get; set;}` – derHugo Apr 21 '21 at 06:50
2

I will assume the Json Is the representation of a traduction in multiple language. and can have multiple sentence/label translated in a return.
And you don't want a class for play_classic and for play_hard, that will hold the same Language properties. Especially not if adding a language force you to add a property in your frontend and in each and every class.

{
    "play_classic": [
        { "english": "PLAY CLASSIC" },
        { "spanish": "JUEGO CLÁSICO" },
        { "portuguese": "JUEGO CLÁSICO" },
        { "russian": "IGRAT' V KLASSICHESKUYU" }
    ],
    "play": [
        { "english": "PLAY" },
        { "spanish": "JUEGO" },
        { "portuguese": "JUEGO" },
        { "russian": "IGRAT'" }
    ],
    "classic": [
        { "english": "CLASSIC" },
        { "spanish": "CLÁSICO" },
        { "portuguese": "CLÁSICO" },
        { "russian": "KLASSICHESKUYU" }
    ]
}

I will choose Dictionary<string, List<Dictionary<string, string>>> as destination object. And will flattern the inner list like : Dictionary<string, Dictionary<string, string>>.
Note: You will have to additionally install/import Newtonsoft .Net JSON into your project since the built-in JsonUtility doesn't support Dictionary. Comment by derHugo, user:7111561

var resultRaw = JsonConvert.DeserializeObject<Dictionary<string, List<Dictionary<string, string>>>>(input);

var resultFlattern = resultRaw.ToDictionary(
                                x => x.Key, 
                                x=> x.Value.SelectMany(trd=> trd)
                                .ToDictionary(kvp=> kvp.Key, kvp=> kvp.Value)
                            );

Usage : given the button play_classic and the langue "portuguese"
Direct access will be as simple as resultFlattern[buttonName][language]

var buttonName="play_classic"; var language= "portuguese";

if(resultFlattern.TryGetValue(buttonName, out Dictionary<string, string>  allTrads))
{
    if(allTrads.TryGetValue(language, out string traduction)){
        Console.WriteLine($"Traduction for button [{buttonName}] in [{language}] is : [{traduction}]") ;
    }
    else{
        Console.WriteLine($"Cant find tanslation for button : [{buttonName}] in [{language}]") ;
    }
}
else{ 
    Console.WriteLine($"Cant find tanslation for button : [{buttonName}]");
}

Demo : https://dotnetfiddle.net/FcxtUp

I will wrap that into a nice helper that load all traductions only once. And give the translation for a given language and button/label. Il will also fire proof it with a default return to Label name when the traduction is not found.

If language is not something easy to cahnge you can faltern the resul one more time and only keep one translation in Dictionary<string, string>. LabelName/Translation

Self
  • 349
  • 1
  • 8
  • 1
    Nice example and solution. – Nikola Jokovic Apr 20 '21 at 13:58
  • Note that for this you have to additionally install/import `Newtonsoft .Net JSON` into your project since the built-in `JsonUtility` doesn't support `Dictionary` ;) .. in general note that for this kind of localization the usage of a `.CSV` is way more convenient though ;) – derHugo Apr 21 '21 at 06:44
  • @derHugo, there was an `JsonConvert.DeserializeObject(json);` in OP code and Op had a comment like "_it is only works with `JsonConvert.DeserializeObject`...this is unity_". I assume it was available. – Self Apr 21 '21 at 06:47
  • Added your comment into the answer, with proper attribution. – Self Apr 21 '21 at 06:55
  • @Self oh didn't note that. I stand corrected ;) seems that OP already did that (or Unity imports that package by default in newer versions) – derHugo Apr 21 '21 at 06:55