0
\$\begingroup\$

I'm using a JSON file for loading texts and dialogs and here's my JSON file:

{
  "Story":
  [
    {"Name":"FirstEncounter", "Content":[
      {"id":0, "text":"sample"},
      {"id":1, "text":"sample2"}
    ]},
    {"Name":"LaGrandDuel", "Content":[
      {"id":0, "text":"sample"},
      {"id":1, "text":"sample2"}
    ]}
  ],
  "Quest1":
  [
    {"Name":"FirstEncounter", "Content":[
      {"id":0, "text":"sample"},
      {"id":1, "text":"sample2"}
    ]},
    {"Name":"QuestDetail", "Content":[
      {"id":0, "text":"sample"},
      {"id":1, "text":"sample2"}
    ]}
  ]
}

And I can load it using JsonUtility like this:

private void Start()
{
    Texts = Resources.Load<TextAsset>("Texts/Test").text;
    Root JsonData = JsonUtility.FromJson<Root>(Texts);
}

and here are the classes I use:

[System.Serializable]
public class Content
{
    public int id;
    public string text;
}

[System.Serializable]
public class Story
{
    public string Name;
    public List<Content> Content;
}

[System.Serializable]
public class Quest1
{
    public string Name;
    public List<Content> Content;
}

[System.Serializable]
public class Root
{
    public List<Story> Story;
    public List<Quest1> Quest1;
}

So setting up the whole thing is done. But, for instance, how can I access the Content related to FirstEncounter in Story? Root and Story classes use a list, not a dictionary. So I'm completely clueless about how to access different contents.

Sorry if the question seems a bit silly, I'm pretty new to JSON files.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ If you are setting your project still up, have you considered using i18n right from the start to make your life easier if you want to port it to more than one language? \$\endgroup\$
    – Zibelas
    Mar 8, 2022 at 17:55
  • \$\begingroup\$ Yes, the main reason I'm using JSON files is to translate the game to other languages. Though, I can't understand how your question relates to what I've asked \$\endgroup\$
    – Arian_ki
    Mar 8, 2022 at 18:00
  • 1
    \$\begingroup\$ I think what Zibelas is referring to is that rather than storing a string containing the English text (or French, or...), you would store an localization ID. That ID would then let you index into a database of localized texts for the currently selected language. So your story/quest structure remains constant across localizations, all you're doing is swapping out the text database. That way, if you change the order of quests, you make that change in one file and it affects all languages, and you don't have to make sure you've applied the same change to every individual language file separately. \$\endgroup\$
    – DMGregory
    Mar 8, 2022 at 18:07
  • \$\begingroup\$ Oh, I see. That's a great idea! My initial thought was to create several copies of this JSON file and edit them but your method is both fast and accurate \$\endgroup\$
    – Arian_ki
    Mar 8, 2022 at 18:14
  • \$\begingroup\$ In case you want to read up about it a bit: docs.unity3d.com/Packages/[email protected]/manual/… \$\endgroup\$
    – Zibelas
    Mar 8, 2022 at 19:22

1 Answer 1

1
\$\begingroup\$

how can I access the Content related to FirstEncounter in Story?

Well, you can always use List.Find:

[System.Serializable]
public class Root
{
    public List<Story> Story;
    public List<Quest1> Quest1;

    public List<Content> GetStoryContent(string storyName) {
        var match = Story.Find(s => s.Name == storyName);
        return match?.Content;
    }
}

But if you like the convenience of a dictionary, then why not just add a dictionary?

[System.Serializable]
public class NamedList<T> {
    public string name;
    public List<T> content;
}

[System.Serializable]
public class Collection<T> : ISerializationCallbackReceiver {
    public List<NamedList<T>> collection;

    Dictionary<string, List<T>> _contentLookup;

    public bool TryGetContent(string name, out List<T> content) {
        return _contentLookup.TryGetValue(name, out content);           
    }

    public void OnBeforeSerialize() {}

    public void OnAfterDeserialize() {
        _contentLookup = new Dictionary<string, List<T>>(collection.Count);
        foreach(var item in collection) {
            _contentLookup.Add(item.name, item.content);
        }
    }
}

Using generics this way, we don't need separate Story and Quest classes (even if they use different types of content), so we can avoid writing the dictionary boilerplate twice. Your Root class then becomes:

[System.Serializable]
public class Root {
    public Collection<Content> story;
    public Collection<Content> quest;
}

The ISerializationCallbackReceiver interface should cause the Unity deserializer used by JsonUtility.FromJson<T>() to call OnAfterDeserialize() on your nested types automatically.

\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .