Saving and loading JSON data

I have set up some simple form controls like radio buttons, sliders, checkboxes, and other similar things. I want to be able to load data from a JSON file. Here’s some example JSON data:

[
  [
    {
      "type": "text",
      "text": "Hello world! This text is on page 1"
    }
  ],
  [
    {
      "type": "text",
      "text": "Page 2 has text and a slider"
    },
    {
      "type": "slider",
      "minValue": "0.0",
      "maxValue": "100.0",
      "defaultValue": "50.0",
      "minLabel": "Smallest Value",
      "midLabel": "Middle Value",
      "maxLabel": "Largest Value"
    }
  ]
]

I want my program to be able to read data from this JSON file into an object like an array of arrays of dictionaries so that I can pass the JSON data to the functions that I use to create the objects onscreen, but this has been giving me a lot of trouble. The main issue I ran into is that the Unity JsonUtility doesn’t seem to support dictionaries, according to someone on stackoverflow. I tried a bunch of different things but even when only using arrays of strings, I couldn’t get the JsonUtility to actually save or load anything - if I created an array in C# and tried to save it as a JSON string, I just got “{}”. I tried other libraries like FullSerializer but they were unsuccessful as well. Another issue is that C# doesn’t do anonymous dictionaries, so all of my data has to be stored as a string (as seen above), even floats, integers, and booleans. I’m a JavaScript developer and this seems like it should be a really simple task to me but I cannot seem to make C# do what I want it to do. Is there something I am doing wrong? What is the correct way to store this data in a text file that can be written to by other programs? My end goal is to write a separate application in PHP/JS that generates the files and then the Unity application reads the data via an HTTP GET request and generates the appropriate elements onscreen based off what it receives.

Thanks

1 Like

I would suggest using json.net.
You could use a site like https://json2csharp.com/ to convert from your string to a class, but looking at your json, you might want to change it up a little to get it into a proper format.

But the main thing is, json.net is a better json library vs jsonutility.

Well, as it has been said already you can not use the JsonUtility of Unity because it has strict serialization rules.

  • The root object always have to be an object. So you can not have an array at the top
  • Arrays of arrays are not supported. You can have an array of a class that contains another array, that’s fine, but directly nested arrays are not supported.
  • Classes need to be marked with the Serializable attribute.
  • Of course since objects are represented by C# classes, the key names of an object are restricted to valid variable identifiers. Json itself allows any string as key, JsonUtility does not.
  • Arrays in C# are typed. So an array of something can only have that something in it. So you can not have different types mixed in an array.

There are generally two ways how to access / parse json data.

  • Either map the data to actual arrays / objects (classes)
  • Or just parse the data into arrays / dictionaries

As already mentioned Unity’s JsonUtility class is often not suitable for such cases as it kinda forces you to use a certain data structure it can actually handle. That’s why most people use an external Json library. One of the most famous one is Newtonsoft’s Json.NET library. It supports all sorts of data and wayst how to deserialize json data. It might be a bit tricky to setup. In general your current approach to structure your data requires some kind of polymorphism if one wants to represent the data in a single array. This can be quite tricky to setup. For example your “page” array contains different kinds of objects. First you have a text component and then additional components like your slider. So setting up a parser for this is possible but also tricky and requires a lot setup to make sure the parser can create the correct types.

Alternatively you can use a pure json parser that parses the data into an generic array / dictionary structure and process the data yourself. I’ve written this SimpleJSON parser which does exactly that. It has custom classes to represent the different json types and for the most part an easy interface to the data with a lot of automatic conversion operators which should make your life easier. Note that the class(es) are all partial classes so they can easily extended by “modules”. The whole framework is just a single file, though If you look into the repository I made some extensions. One for some Unity primitive types (like vectors) and another one for some more .NET types (like DateTime). Extensions just need to be present next to the main file. With it you can do

using SimpleJSON;

// [ ... ]

var pages = JSON.Parse(yourJsonText);
foreach(JSONNode page in pages)
{
    foreach(JSONNode item in page)
    {
        string type = item["type"];
        if (type == "text")
        {
            string text = item["text"];
            // [ ... ]
        }
        else if (type == "slider")
        {
            float minValue = item["minValue"];
            float maxValue = item["maxValue"];
            float defaultVal = item.GetValueOrDefault("defaultValue", (minValue + maxValue)*0.5f);
            string minLabel = item["minLabel"];
            string midLabel = item["midLabel"];
            string maxLabel = item["maxLabel"];
            // [ ... ]
        }
    }
}

As you can see everything is essentially a JSONNode which has several derived classes (JSONObject, JSONArray, JSONString, JSONNumber, JSONBool and JSONNull). When you try to assign a JSONNode to an actual type, if an implicit type conversion operator exists, it tries to convert that value into that desired type. Note as an example I used the method “GetValueOrDefault” once to show that you can implement proper default value handling that way. So here if no “defaultValue” was specified it would use the center of the slider range as default value.

When you look into the Unity extension file you can see that for example vectors can be represented either by an array or a class (with keys “x”,“y”,“z”). To simplify parsing you could implement your own type conversion operators (don’t need to be implicity, could be explicit) to actually convert a whole json object into some other concrete C# type. Though that’s up to do. This parser accepts any valid json and the names of the keys do not matter.

3 Likes

Json.Net is great, you can download it from assetstore

Here is a sample code.

var json = File.ReadAllText(filePath);
var result = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
2 Likes

+1 For NewtonSoft’s JsonReader

"minValue": "0.0",
"maxValue": "100.0",
"defaultValue": "50.0",

This bad! You need proper data-types here! And a documentation what type one can expect…
Or put the to be expected datatype in the variables name.
(e.g. “float_MaxSliderValue” : 22.2 or something like this)
Be very, very clear with your names and you make it easier for everyone involved (even yourself in 4yrs time).

With your version you have the values as string - you have to parse the string and make sure you read it in, in the correct culture…

eulerAngles = new Vector3(
  float.Parse(stringValue.Rotation[0], NumberStyle, CultureInfo),
  float.Parse(stringValue.Rotation[1], NumberStyle, CultureInfo),
  float.Parse(stringValue.Rotation[2], NumberStyle, CultureInfo)
);

You would only need to (array of 3 floats):

new Vector3(floatValue.Rotation[0], floatValue.Rotation[1], floatValue.Rotation[2]);

This is for an array of floats… If you name them correctly x,y,z (example below) Newton could even map it directly into a Vector3!

Make proper objects with good names, a “mapping” class and let Newton do all the work of converting the stuff around. You can then use the very same class to export the data out as well.

Something like this:

{
    "pages": [
        {
            "uint_PageNumber": 1,
            "pageTitle": "translationKey::page1Title"
        },
        {
            "uint_PageNumber": 2,
            "pageTitle": "translationKey::page2Title",
            "controls": [
                {
                    "controlType": "slider",
                    "label": "translationKey::playerhealth",
                    "float_SliderMinValue": 0,
                    "float_SliderMaxValue": 100,
                    "float_SliderDefaultValue": 50,
                    "nullable_SliderMinLabel": "translationKey::smallestvalue",
                    "nullable_SliderMidLabel": "translationKey::middlevalue",
                    "nullable_SliderMaxLabel": "translationKey::largestvalue",
                    "vector3f_WorldPosition" :
                    {
                        "x": 0.1,
                        "y": 0,
                        "z": 0
                    }
                },
                {
                    "controlType": "slider",
                    "label": "translationKey::playermana",
                    "float_SliderMinValue": 0,
                    "float_SliderMaxValue": 123.4,
                    "float_SliderDefaultValue": 0,
                    "vector3f_WorldPosition" :
                    {
                        "x": 1.75,
                        "y": 0,
                        "z": 0
                    }
                }
            ]
        }
    ]
}
public class MenuPages
{
    public PagesData[] Pages {get;set;}
  
    public class PagesData
    {
        public uint Uint_PageNumber {get;set;}
        public string String_PageTitle {get;set;}
        public ControllerData[]? Controls{get;set;}

        public class ControlData
        {
            public string ControlType {get;set;}
            public string Label {get;set;}
            public float Float_SliderMinValue {get;set;}
            public float Float_SliderMaxValue {get;set;}
            public float Float_SliderDefaultValue {get;set;}
            public string? Nullable_SliderMinLabel {get; set;}
            public string? Nullable_SliderMidLabel {get; set;}
            public string? Nullable_SliderMaxLabel {get; set;}
            public Vector3 Vector3f_WorldPosition { get;set;}
        }
    }
}

The variables with a ? are Nullables - you can tell Newton to ignore them when reading/writing.
Lists also work instead of arrays.

After that you can then fill you UI classes with ease.

But most important always be very clear what kind of data a user can expect. Give good names - think twice if you really need all the stuff keep everything in a proper order and structure and finally:
Think of your data this way: With the exact same data you should be able to create the same stuff in Unity, Unreal, Goddot and what ever else is out there.
Never let an Engine or Programming language dictate how your data is structured.

Hope this helps.

I think you missed an important point. He has a double nested array. The outer array is an array of “pages” each page is another array that contains UI elements like the first one is of type text, the second one may be a slider like in his example. So the elements inside the page array could have various types. So your example is a bit off from the original :slight_smile: Such a strict format as you have it would not really work. Different UI elements have different properties. So a “button” may have an onclick property. A combobox may have a list of options, etc. At least that’s what I read out of the example he provided. As I said, Newtonsoft’s parser does support polymorphism, but you have to specify contracts or custom converter as explained in the SO question I had linked above.

Wow, thanks so much for the detailed response! I tried the Newtonsoft library before but ran into a lot of issues trying to import the NuGet package in Unity. Your SimpleJson library looks perfect for what I am doing, so I will give that a try. If that doesn’t work I will try Newtonsoft again and see if I can get it working.

Your library worked like a charm! I can’t thank you enough!

Thats exactly why i mentioned the nullables and showed the proper classes to load that stuff. The deserialization will work flawlessly and i mentioned a few key points to watch out for (proper data, variable-names).
Page 1 has no slider, Page 2 has two sliders. A slider can but must not have min, mid, max labels.
Of course If there is a need for different controls it would be better to make explicit slidercontrol-, buttoncontrol-classes. And still no need for any converters -

“So a “button” may have an onclick property” - don’t call it that.
Thats what a Programming-Language or an Engine may call it. Just think a bit into the future and you have a few hundred Json-Files all having a magnitude of “Buttons” with OnClick-Properties. But for some reason you want to change the execution from OnClick to OnPointerDown or HeadSetTouched or whatever…
You now have a created a contradicting “connection” between your data and the actual implementation because you did not really detach the data.
Yes, this example sound minor but believe me that stuff stacks up very quickly and you will have to create an additional and totally unnecessary documentation to explain why nothing really fits together. And this is still only the loading part.

Not really something that should be here but as for the Imgur-Problem… you could load it like this:

class ImgurResponseImageOrAlbum
    {
        public string Success { get; set; }
        public int Status { get; set; }
        class Data
        {
            public string Id { get; set; }
            public string Title { get; set; }
            public string? Description { get; set; } // can be null or missing
            public System.DateTime DateTime { get; set; }
            public bool IsAlbum { get; set; }
            public int Ups { get; set; }
            public int Downs { get; set; }
            public int Points { get; set; }
            public int Score { get; set; }

            //... some more stuff that is EQ in both respones

            // Album only stuff here
            public string? Cover { get; set; }
            // ... other album specific stuff - all as nullables

            // Img only stuff here
            public string? Type { get; set; }
            public ImageData[]? Images { get; set; }
            public class ImageData
            {
                public string Id { get; set; }
                public string? Title { get; set; }
            }
            // ... other image specific stuff - all as nullables
        }
    }

What do you mean by that? If this is an UI definition file that is supposed to create an UI you would have probably dozens of different control types and you just want to have a list with controls. Or do you suggest to have a seperate list for each control type? How would you arranged them in a sequence then?

To be fair, we don’t know exactly what he needs. The json he posted was just an example. However at the moment he clearly has 2 different controls, a text control and a slider control and they are in the same array. Though he mentioned others in the beginning.

You haven’t really provided any good reason or alternative name. So do you think that since Unity called the OnClick handler of it’s UI button “OnClick” that this is a bad name? I mean when we want to construct an UI from our json data, why would the 1-to-1 mapping of names and properties be confusing :). If you think your UI may need an additional OnPointerDown handler, sure, why not. But that would simply be an option you can use in the json file. Since you brought that up, what would you suggest to call the binding value that you give a button? Buttons are usually meant to be clicked “logically”. If you need some extra special behaviour you could always think of new / custom controls (like images, weblink, some kind of grouping element, scrollview, …).

Well, you seem to assume that this data belongs to a fix alteady existing structure / layout? Like a website with a fix layout and you just fill it with data? That’s very unlikely here.

So the whole structure comes from the json file, it seems. At least that’s what I understood.

I just didn’t expand the example further than controls because i simply didn’t want to overburden someone that is just starting.
You can make an array with multiple different objects, no problem. Don’t call it Controls (thou he only mentioned controls - call it “Content” and put the stuff in there.

I didn’t suggest that. The Controls/Content could take anything. As long you keep everything nullable you can read every element exactly the same way. Exactly as with the Album/Image Problem above.
Having the elements in separate lists would work as well. Just give them an explicit Index-Number and everything is fine. But who know how the final UI will look like - maybe even that does not matter.
But i also added the vector3.
With that you could arrange them. While i called it WorldPos it could be used inside of a “smaller” world. (Design it in the Web - it gets added into a Pipboy or as a VR 360° Menu)

Thats why i rearranged his JSON a little and put them in “Pages[ ]” It seems every Page has a at least title (i added a page number in case you want to rearrange these quickly (testing)) and an optional controls array.
It is an example the he could start to build his own thing. Not a solution.
I tried to put as much info and hints into it to give him a good start and some extra pointers where to think twice before just implementing stuff. So, if he hopefully reads through it - he will get the basic idea and can expands on it. With proper naming, proper structure and also an idea on how to properly load that stuff into Unity with ease.

I did provide a very good reason and had a hint to a possible name.
Again the reason is: You have something you want to be executed by OnClick in Unity.
You map that 1-1 in your Json: “onclick”:“ExecuteAction”.

Now you release what ever you do, but after some Beta-Feedback you notice that OnClick isn’t that good.
You actually want the execution on OnPointerDown now. In an instant your 1-1 mapping just became worthless. Worse than that - you now has a discrepancy. Everything that is called onClick in the Json is now executed in the OnPointerDown.
Yes, you could export every single Json again with the onClick replaced by onPointerDown…

But as time goes on - you also expand your GUI System to VR you want the onClick now in the onHeadSetTouched and alternatively onFistClosed, when Handtracking is enabled. As for the mobile-version the same command should execute a right-swipe.
Does this make it clearer why you should always keep data as separated from implementation as possible?
You would not have all the above problems and there would be no need to re-export json-files.

Forget the onClick and replace it with: “Command”, “Action”, “Execute” or something similar (Yeah i only hinted at a possible name).
The data does not care where and how you implement it. The data only tells you - this is the “thing/action/command” you must “do/execute” - not how or when.
How you do it, in what engine you do it, in what language you do it, does and should never matter.
Data should never dictate how something must look or what interfaces you use to interact with your application and if you really must define something engine specific - it must be purely optional.

Otherwise if it is so strongly tied to Unity… why even bother? Just make a remote call to a Unity-Editor and build that GUI there… make an extra scene for the GUI and load it additionally so that there are no overlaps with other scenes.

No, i didn’t assume anything. I just tried to help and show him that he can make it work properly without any strange extra-steps, that he really should think it trough and also think about what kind of future changes can happen.