Search Unity

Working with JSON

Discussion in 'Scripting' started by Necronomicron, Jun 9, 2019.

  1. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    Hi!
    From docs I know how to parse int, float, string or array from JSON. But how can I parse something like this:
    Code (CSharp):
    1. {
    2.   "levels": [
    3.     {
    4.       "grid": [ 20, 1 ],
    5.       "set": [
    6.         {
    7.           "type": "1D",
    8.           "point1": [ 0, 0 ],
    9.           "point2": [ 19, 0 ],
    10.           "color1": "0087BD",
    11.           "color2": "FF7813",
    12.           "colorSpace": "hsv"
    13.         }
    14.       ],
    15.       "locked": [
    16.         [ 0, 0 ],
    17.         [ 19, 0 ]
    18.       ]
    19.     },
    20.     {
    21.       "grid": [ 10, 7 ],
    22.       "set": [
    23.         {
    24.           "type": "1D",
    25.           "point1": [ 0, 0 ],
    26.           "point2": [ 9, 0 ],
    27.           "color1": "0087BD",
    28.           "color2": "FF7813",
    29.           "colorSpace": "hsv"
    30.         },
    31.         {
    32.           "type": "2D",
    33.           "point1": [ 2, 0 ],
    34.           "point2": [ 4, 2 ],
    35.           "color1": "FACE8D",
    36.           "color2": "666FFF",
    37.           "color3": "123456",
    38.           "color4": "FF7F00",
    39.           "colorSpace1": "hsv",
    40.           "colorSpace2": "rgb",
    41.           "colorSpace3": "lab",
    42.           "order": "h"
    43.         },
    44.         {
    45.           "type": "1D",
    46.           "point1": [ 0, 6 ],
    47.           "point2": [ 9, 6 ],
    48.           "color1": "BD0087",
    49.           "color2": "13FF78",
    50.           "colorSpace": "rgb"
    51.         }
    52.       ],
    53.       "locked": [
    54.         [ 0, 0 ],
    55.         [ 9, 0 ],
    56.         [ 0, 2 ],
    57.         [ 2, 2 ],
    58.         [ 0, 4 ],
    59.         [ 2, 4 ],
    60.         [ 0, 6 ],
    61.         [ 9, 6 ]
    62.       ]
    63.     }
    64.   ]
    65. }
    And should I actually use JSON or something else for this? I tried to parse it myself but it seems Unity doesn't support some complicated objects.
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    You can use JsonUtility.
    You can convert class to Json and parse Json back class, providing it has correct format.
     
  3. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    I just told I used that. But it seems it can't work with array of arrays, for example.
     
  4. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    Doesn't mean you work with JsonUtility, that why I suggested it.

    No you can't work with array, or dictionary, but you can work with lists.
    List can take other class with list, or other fileds inside. etc. So you can nest them as you like.
     
  5. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    You need to create C# classes that mimic your JSON objects.
    For instance, if your JSON object contains an array of objects, then your C# class needs to contain an array of objects as well. If your JSON object contains an array of objects that contain another array of objects, then your C# class needs to as well. So-on and so-forth.

    Because JSON is loosely-typed and C# is strongly-typed, things like this will not serialize...
    Code (CSharp):
    1. {
    2.    "color1": "etc...",
    3.    "color2": "etc..."
    4. },
    5. {
    6.    "color1": "etc...",
    7.    "color2": "etc...",
    8.    "color3": "etc...",
    9.    "color4": "etc..."
    10. }
    ...Because it's not possible for instances of the same type of C# object to have a dynamic amount of fields. This can be fixed by turning your multiple fields into a single array of strings:
    Code (CSharp):
    1. {
    2.    "colors": [
    3.       "etc...",
    4.       "etc..."
    5.    ]
    6. },
    7. {
    8.    "colors": [
    9.       "etc...",
    10.       "etc...",
    11.       "etc...",
    12.       "etc..."
    13.    ]
    14. }
    The same thing should be done with your
    point1, point2, etc...
    and
    colorSpace1, colorSpace2, etc...
    fields.
    All of your JSON objects need to have a consistent amount of fields. I noticed that in one of your embedded
    set
    objects, you have a field called "order". You may only need an "order" value for this one specific
    set
    object, but the fact that this field exists, means it needs to exist in all
    set
    objects.

    With that, your data should look like this:
    Code (CSharp):
    1. {
    2.   "levels": [
    3.     {
    4.       "grid": [ 20, 1 ],
    5.       "sets": [
    6.         {
    7.           "type": "1D",
    8.           "points": [
    9.               [ 0, 0 ],
    10.               [ 19, 0 ]
    11.            ],
    12.           "colors": [
    13.               "0087BD",
    14.               "FF7813"
    15.            ],
    16.           "colorSpaces": [
    17.               "hsv"
    18.            ],
    19.           "order": null
    20.         }
    21.       ],
    22.       "locked": [
    23.         [ 0, 0 ],
    24.         [ 19, 0 ]
    25.       ]
    26.     },
    27.     {
    28.       "grid": [ 10, 7 ],
    29.       "sets": [
    30.         {
    31.           "type": "1D",
    32.           "points": [
    33.               [ 0, 0 ],
    34.               [ 9, 0 ]
    35.            ],
    36.           "colors": [
    37.               "0087BD",
    38.               "FF7813"
    39.            ],
    40.           "colorSpaces": [
    41.               "hsv"
    42.            ],
    43.           "order": null
    44.         },
    45.         {
    46.           "type": "2D",
    47.           "points": [
    48.               [ 2, 0 ],
    49.               [ 4, 2 ]
    50.            ],
    51.           "colors": [
    52.               "FACE8D",
    53.               "666FFF",
    54.               "123456",
    55.               "FF7F00"
    56.            ],
    57.           "colorSpaces": [
    58.               "hsv",
    59.               "rgb",
    60.               "lab"
    61.            ],
    62.           "order": "h"
    63.         },
    64.         {
    65.           "type": "1D",
    66.           "points": [
    67.               [ 0, 6 ],
    68.               [ 9, 6 ]
    69.            ],
    70.           "colors": [
    71.               "BD0087",
    72.               "13FF78"
    73.            ],
    74.           "colorSpaces": [
    75.               "rgb"
    76.            ],
    77.           "order": null
    78.         }
    79.       ],
    80.       "locked": [
    81.         [ 0, 0 ],
    82.         [ 9, 0 ],
    83.         [ 0, 2 ],
    84.         [ 2, 2 ],
    85.         [ 0, 4 ],
    86.         [ 2, 4 ],
    87.         [ 0, 6 ],
    88.         [ 9, 6 ]
    89.       ]
    90.     }
    91.   ]
    92. }

    Based on this JSON data, you can then mimic it in C# with a structure like this:
    Code (CSharp):
    1. public class SomeClass {
    2.    Level[] levels;
    3. }
    Code (CSharp):
    1. public class Level {
    2.    int[] grid;
    3.    Set[] sets;
    4.    int[][] locked;
    5. }
    Code (CSharp):
    1. public class Set {
    2.    string type;
    3.    int[][] points;
    4.    string[] colors;
    5.    string[] colorSpaces;
    6.    string order;
    7. }
     
    Last edited: Jun 10, 2019
  6. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,187
    You can also always throw your json string into http://json2csharp.com/
    And it will show you your class structure. I know Visual Studios even has a feature for it that does something similar.

    The only difference is the json site uses properties for json.net while JsonUtility uses fields, but at least it gives you an idea of your structure.
     
    Wekthor and Necronomicron like this.
  7. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    Is there anything else to work with JSON in Unity? I thought it's the only way.

    I remember it parsed array yesterday, but couldn't parse list of lists.
     
  8. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    Yep, reading file and having own written parser, or using many of other existing libraries on the net.
    Before JsonUtility been added to Unity, people were using different routes, to parse in/out JSON.

    Actually you may be able do array, sorry haven't touched them with JSON much. However, normally you won't be able to do dictionaries. But for lists, definitely you need use own class as a type.
    Then in that class, you can have another list (s).
    Just need to remember, about adding [serializable] above class (es).
     
    Necronomicron likes this.
  9. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    Well, of course, I meant some built-in tool aside JsonUtility.
     
  10. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,976
    JSON is just a text file, so before jsonutility everyone was mostly reading it same as you would any other text file, using a textreader + textwriter or streamreader + streamwriter etc
    Jsonutility simply makes it easier, but its not a necessity and you shouldnt assume everyone is using it when they say JSON ;)
     
  11. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    I actually don't recommend the POCO approach- it works well in web development and normal software engineering, since the data types don't tend to change very frequently, but game development projects tend to have really fast iterations and the data stored can change pretty dramatically from one month to the next. Since JsonUtility (last time I checked) can only work with POCO objects to deserialize into, I don't actually like using it outside of trying to make demos with as few dependencies as possible.

    If you're absolutely dead-set on using JsonUtility, use Vryken's method, or rather, that's really the only method you CAN use I think, but if you're looking to make JSON deserialization a bit less rigid, download and use Json.NET from Newtonsoft instead (or Json.NET for Unity on the Asset Store, though that hasn't been updated in awhile). That'll allow you to do incremental deserialization (line by line), or use the "dynamic" type for accessing your members without needing to make a POCO for each level in the data hierarchy.

    In Vryken's example (which is completely standard for JsonUtility usage), you need the SomeClass that holds a collection of Levels, which hold a collection of Sets, which hold most of the data. Not only do you have to define each level of the hierarchy as its own object type, but if you update the members of those types in the future, you have to be very careful not to break compatibility with existing save files. That's fine in early testing, when you can just throw the files away and recreate them, or when the changes are small enough (simple name changes with the FormerlySerializedAs attribute, for instance, assuming that even works with JsonUtility?), but if you need some way to parse old data that the classes no longer need/have defined as members, in order to convert it to something that it DOES need, then things get really complicated.

    In those instances, JsonUtility just doesn't cut it IMO. But if you make a dynamic object, you can pass that object into various functions based on the version, in order to modify the data to something the next step understands. For instance, version .11 of save data gets passed into FunctionA to convert to .12, this changes some of the values/types to make it compatible with that version, then pass it to FunctionB to convert the now-.12 save data to .13, and so on and so forth. You can also do this using a custom JsonConverter, or deserializing manually line-by-line, so the "dynamic" isn't strictly necessary, but makes this process a bit faster/easier. Regardless, this is something that's really difficult to do with the POCO approach, in my experience, though ultimately it would get fed into a POCO object for the rest of the application to understand, once the data has been converted to the most recent version.

    Vryken's answer is definitely correct here, so these are just some things to think about if you continue going down this path. Worth noting that most of these features are available with the built-in XML serializer, which was the primary reason it took me so long to make the jump to JSON.

    But no, there are no other built-in tools in Unity to parse Json other than JsonUtility. If you need more control over the serialization/deserialization process, but have to use built-in tools, use the binary or XML serializers from .NET/Mono instead, or read the data as text and parse it yourself (ick).
     
  12. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    I ended up with this:
    Code (CSharp):
    1. {
    2.   "levels": [
    3.     {
    4.       "map": "0001",
    5.       "instructions": [
    6.         {
    7.           "coords": [ 4, 2, 8, 8 ],
    8.           "colorSpaces": [ 1, 0, 0 ],
    9.           "order": 1
    10.         },
    11.         {
    12.           "coords": [ 0, 5, 4, 5 ],
    13.           "colorSpaces": [ 0 ],
    14.           "order": 0
    15.         },
    16.         {
    17.           "coords": [ 1, 5, 1, 7 ],
    18.           "colorSpaces": [ 0 ],
    19.           "order": 0
    20.         },
    21.         {
    22.           "coords": [ 2, 0, 2, 5 ],
    23.           "colorSpaces": [ 0 ],
    24.           "order": 0
    25.         },
    26.         {
    27.           "coords": [ 8, 6, 13, 6 ],
    28.           "colorSpaces": [ 0 ],
    29.           "order": 0
    30.         },
    31.         {
    32.           "coords": [ 11, 0, 11, 6 ],
    33.           "colorSpaces": [ 0 ],
    34.           "order": 0
    35.         },
    36.         {
    37.           "coords": [ 1, 5, 1, 7 ],
    38.           "colorSpaces": [ 0 ],
    39.           "order": 0
    40.         },
    41.         {
    42.           "coords": [ 11, 4, 13, 4 ],
    43.           "colorSpaces": [ 0 ],
    44.           "order": 0
    45.         },
    46.         {
    47.           "coords": [ 2, 0, 11, 0 ],
    48.           "colorSpaces": [ 1 ],
    49.           "order": 0
    50.         },
    51.         {
    52.           "coords": [ 6, 8, 6, 12 ],
    53.           "colorSpaces": [ 1 ],
    54.           "order": 0
    55.         },
    56.         {
    57.           "coords": [ 6, 0, 6, 2 ],
    58.           "colorSpaces": [ 0 ],
    59.           "order": 0
    60.         },
    61.         {
    62.           "coords": [ 12, 6, 12, 10 ],
    63.           "colorSpaces": [ 0 ],
    64.           "order": 0
    65.         },
    66.         {
    67.           "coords": [ 6, 10, 12, 10 ],
    68.           "colorSpaces": [ 0 ],
    69.           "order": 0
    70.         },
    71.         {
    72.           "coords": [ 2, 11, 6, 11 ],
    73.           "colorSpaces": [ 0 ],
    74.           "order": 0
    75.         }
    76.       ],
    77.       "locked": [
    78.         { "point": [ 13, 4 ] },
    79.         { "point": [ 13, 6 ] },
    80.         { "point": [ 2, 0 ] },
    81.         { "point": [ 11, 0 ] },
    82.         { "point": [ 6, 2 ] },
    83.         { "point": [ 6, 8 ] },
    84.         { "point": [ 4, 5 ] },
    85.         { "point": [ 8, 5 ] },
    86.         { "point": [ 0, 5 ] },
    87.         { "point": [ 1, 7 ] },
    88.         { "point": [ 6, 10 ] },
    89.         { "point": [ 6, 11 ] },
    90.         { "point": [ 12, 10 ] }
    91.       ]
    92.     }
    93.   ]
    94. }
    Code (CSharp):
    1. [System.Serializable]
    2. public class DataJSON
    3. {
    4.     public LevelJSON[] levels;
    5.  
    6.     public static DataJSON CreateFromJSON(string data) { return JsonUtility.FromJson<DataJSON>(data); }
    7. }
    Code (CSharp):
    1. [System.Serializable]
    2. public class LevelJSON
    3. {
    4.     public string map;
    5.     public GradientJSON[] instructions;
    6.     public LockedPoint[] locked;
    7. }
    Code (CSharp):
    1. [System.Serializable]
    2. public class GradientJSON
    3. {
    4.     public int[] coords;
    5.     public int[] colorSpaces;
    6.     public int order;
    7. }
    Code (CSharp):
    1. [System.Serializable]
    2. public class LockedPoint
    3. {
    4.     public int[] point;
    5. }
    If you have ideas how to improve it, I'm all ears.
     
  13. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    Seams fine, as long it works.
    Only not sure, why you need static inside first class. But you may have good reason for it.