Get Off My Lawn Get Off My Lawn - 4 months ago 34
JSON Question

Decode Json Object with unknown keys

I have a json object, and I have no idea what the keys in the object will be, is there a way to decode an object like this with Unity's JsonUtility?

Here is what I have, but it doesn't work:

[RequireComponent(typeof(Text))]
public class GameSmartTranslate : MonoBehaviour {

public string translationKey;

Text text;

// Use this for initialization
void Start () {
text = GetComponent<Text>();
GetTranslationFile();
}

void GetTranslationFile(){
string lang = GameSmart.Web.Locale(true);
TextAsset transFile = Resources.Load<TextAsset>("Text/" + lang);
Trans items = JsonUtility.FromJson<Trans>(transFile.text);
}

}

[System.SerializableAttribute]
class Trans {
public string key;
public string value;
}


The test json file looks like this (the files keys will more than likely not be
one
and
two
):

{
"one": "First Item",
"two": "Second Item"
}


The reason the keys are "Unknown" is because this is a json file for translations, and since each game has different gameplay it means the text is different in each game as well, and the number of keys will be different as well. This is also for an sdk that I have to manage that will be put into many games.

Answer

Based on your comments what you really need is two levels of serialization, one level that represents each translated word and another that holds the array of words.

[Serializable]
public class Word{
    public string key;
    public string value;
}

[Serializable]
public class Translation 
{ 
     public Word[] wordList; 
}

This would get translated in to JSON that looks similar to

{
   "wordList": [
      {
         "key": "Hello!",
         "value": "¡Hola!"
      },
      {
         "key": "Goodbye",
         "value": "Adiós"
      }
   ]
}

Once you deserialize your Translation object you can convert it in to a dictionary for faster lookups.

Translation items = JsonUtility.FromJson<Translation>(transFile.text);
Dictionary<string, string> transDict = items.wordList.ToDictionary(x=>x.key, y=>y.value);

By making key's the untranslated word it is easy to make a extension method that will look up in the dictionary the translated word but if it can't find one it will use the key.

public static class ExtensionMethods
{
    public static string GetTranslationOrDefault(this Dictionary<string, string> dict, string word)
    {
        string result;
        if(!dict.TryGetValue(word, out result)
        {
            //Word was not in the dictionary, return the key.
            result = word;
        }
        return result;
    }
}

Which you could use like

[RequireComponent(typeof(Text))]
public class GameSmartTranslate : MonoBehaviour {

    public string translationKey;

    Text text;

    void Start() {
        text = GetComponent<Text>();
        Dictionary<string, string> transDict = GetTranslationFile();

        //If the translation is not defined the text is set to translationKey
        text.text = transDict.GetTranslationOrDefault(translationKey);
    }

    Dictionary<string, string> GetTranslationFile(){
        string lang = GameSmart.Web.Locale(true);
        TextAsset transFile = Resources.Load<TextAsset>("Text/" + lang);
        Translation items = JsonUtility.FromJson<Translation>(transFile.text);
        Dictionary<string, string> transDict = items.wordList.ToDictionary(x=>x.key, y=>y.value);
        return transDict;
    }
}

You may want to try moving the dictionary code out of GameSmartTranslate and put it in a singleton game object so the dictionary does not get re-built for every label that has this script.


Update:

You could also try using the json

[{
    "key": "Hello!",
    "value": "¡Hola!"
}, {
    "key": "Goodbye",
    "value": "Adiós"
}]

this would let you get rid of the Translation class and your code would look like

Word[] items = JsonUtility.FromJson<Word[]>(transFile.text);

But I am pretty shire Unity 5.3 does not work directly on the array type, I have not tried with 5.4.