Complex data structures with ScriptableObjects in Unity3D - c#

I am trying to create a data structure like in the picture, where I can easily add and remove the reward type. It is for a roguelike level-up system where basically I want to get a reward for a specific character like a warrior or mage from a specific rarity.
I am more experienced with Lua than Unity and in Lua language, it would look like this,
`{
["Melee"] =
{
[1] = {
[1] = {"Stat", "Strength", 10},
[2] = {"Ability", "Swirl", 1},
},
[2] = {
[1] = {"Stat", "Strength", 50},
[2] = {"Stat", "PhysicalDefense", 10},
},
},
["Healer"] =
{
[1] = {
[1] = {"Stat", "MagicalAttack", 5},
[2] = {"Ability", "Regeneration", 1},
},
[2] = {
[1] = {"Stat", "MagicalDefense", 15},
[2] = {"Ability", "Regeneration", 1},
},
},
}`
then for getting a spesific reward I would do reward = ["Melee][1][1]. However, in Unity, using dictionaries disables scriptableobject's function to add elements inside editor. So how can I create a scriptableobject that I can add elements inside the editor with the same structure?

You don't need dictionaries in order to access items by index.
Simply use a plain array or List and do
public class Warrior : ScriptableObject
{
public Rarity[] rarities;
}
public class Rarity : ScriptableObject
{
public Reward[] rewards;
}
public class Reward : ScriptableObject
{
public enum RewardType { Stat, Ability }
public RewardType type;
public int value;
}
And finally reference these in a
public Warrior[] warriors;
Then if really needed for the first level you can still setup a dictionary on runtime like e.g.
public Dictionary<string, Warrior> Warriors = new ();
private void Start ()
{
foreach(var warrior in warriors)
{
Warriors.Add(warrior.name, warrior);
}
}
Then the rest you would access like e.g.
var reward = Warriors["Melee"].rarities [0].rewards[1];
Debug.Log($"{reward.type} - {reward.name} - {reward.value}");
Have in mind that in c# indices are 0-based
However, if you are more familiar with your way and prefer a general dictionary approach you can totally do so!
You could simply write this as a JSON and can then chose among the various JSON libraries to convert it into a dictionary again.
{
"Melee" : [
[
{
"type":"Stat",
"name":"Strength",
"value":10
},
{
"type":"Ability",
"name":"Swirl",
"value":1
}
],
...
],
"Healer" : ...
}
then you would not use ScriptableObject and only use e.g.
[Serializable]
public class Reward
{
public enum RewardType { Stat, Ability }
public RewardType type;
public int value;
public string name;
}
and then deserialize this into e.g.
var Warriors = JsonConvert.DeserializeObject<Dictionary<string, Reward[][]>>(theJsonString);
var reward = Warriors["Melee"][0][0];
Debug.Log($"{reward.type} - {reward.name} - {reward.value}");
where theJsonString can be read form a file or a web request etc
This example uses Newtonsoft JSON.Net which is available for Unity via the package manager.
Personally I would though rather go with explicitly named fields and classes instead of those jagged arrays like before
{
"Melee" : {
"rarities" : [
{
"rewards" : [
{
"type":"Stat",
"name":"Strength",
"value":10
},
{
"type":"Ability",
"name":"Swirl",
"value":1
}
]
}
]
},
"Healer" : ...
}

Related

How can I deserialize json array?

I have json and an array inside it what looks like:
{
"data" : [
[int],
[...],
[..n]
]
}
Can I get array from "data" using JsonUtility? Or any other way?
For now, I can get "data" using following code:
[System.Serializable]
public class ObjectProperties
{
public int[] data;
public string color;
}
public void LoadFromJson()
{
objectProperties = JsonUtility.FromJson<ObjectProperties>(File.ReadAllText(Application.streamingAssetsPath + "/CubeSettings.json"));
}
And I can get any data but array I need.
In case the structure actually is e.g.
{
"data" : [
[1],
[2],
[3]
]
}
This is a nested array int[][] (an array where each element is an array itself).
So either the JSON should rather look like
{
"data" : [
1,
2,
3
]
}
or - if changing the JSON is not an option for you - your c# structure rather has to be
[System.Serializable]
public class ObjectProperties
{
public int[][] data;
public string color;
}
BUT such nested arrays are not supported by the built-in JsonUtility (see Script Serialization).
For this you would need to use a different library like e.g. Newtonsoft .NET JSON (available as a Package via the Package Manager)
var json = File.ReadAllText(Application.streamingAssetsPath + "/CubeSettings.json"));
var objectProperties = JsonConvert.DeserializeObject<ObjectProperties>(json);
This also means the field will not be visible/editable in the Inspector and will not be saved together with the scene or prefabs in Unity.
I assume 'data' is array itself, but not array of array.
So it should be
{"data":[1,2,3],"color":"red"}
Also you should define get/set to include fields into serialization:
[System.Serializable]
public class ObjectProperties
{
public int[] data { get; set; }
public string color { get; set; }
}
Now you can run it:
var objectProperties = JsonSerializer.Deserialize<ObjectProperties>(...);

Deserializing JSON issue

I need to deserialize just part of a JSON string returned from a server. The 'myData' portion in the JSON string below.
My JSON string is structured as follows.
{
"data": {
"CODE": {
"someData": {
"h": "foo",
"id": "City",
"lat": "11.11111"
},
"feedMe": [
[
{
"myData": {
"item1": "a",
"item2": "b",
"item3": "c"
},
"moreData": {}
}
]
]
}
}
}
In Unity there is the JSONutility.FromJson method
https://docs.unity3d.com/ScriptReference/JsonUtility.FromJson.html
but unsure how I would either
1 pass only the 'myData' portion to this method.
or
2 Deserialize the entire string
An alternativ to using JsonUtility there is good old SimpleJSON which allows you to only access a certain field of your json like e.g.
var N = JSON.Parse(the_JSON_string);
var myData = N["data"]["CODE"]["feedMe"][0][0];
var item2 = myData["item2"].Value;
In general the simplest way to get the needed c# class structure for your json is always using json2csharp and make all classes [Serializable] and remove the {get; set;} in order to use fields instead of properties. Something like this
[Serializable]
public class SomeData
{
public string h;
public string id;
public string lat;
}
[Serializable]
public class CODE
{
public SomeData someData;
public List<List<MyData>> feedMe;
}
[Serializable]
public class MyData
{
public string item1;
public string item2;
public string item3;
}
[Serializable]
public class Data
{
public CODE CODE;
}
[Serializable]
public class RootObject
{
public Data data;
}
Instead of List<T> you can also use T[] if you like. And the class names actually don't matter but the structure and field names have to match.
and then use
var root = JsonUtility.FromJson<RootObject>(THE_JSON_STRING);
var myData = root.data.CODE.feedMe[0][0];
var item2 = myData.item2;
As already comented however there is a nested array in your array .. not sure if this is intended.
well, use one of the powerful json nuget -newtonsoft.json , then in your code you can iterate the values like below
var files = JObject.Parse(YourJSON);
var recList = files.SelectTokens("$..data").ToList();
foreach (JObject obj in recList.Children())
{
foreach (JProperty prop in obj.Children())
{
var key = prop.Name.ToString();
var value = prop.Value.ToString();
//Do your stuffs here
}
}
JsonUtility not work whit json files, this only for save and load basic public variables of some class. Asset Store have many frameworks for parse json. p.s. your json is strange, [] its array and you have feedMe:[[{myData, moreData}]]. One array whene just one object in array... parse confusing.

Is there a way to define classes inline upon their usage?

I'm writing a service to retrieve data from an API over HTTP from another team in my company. The JSON response body from their API looks a bit like this:
"SomeObject": {
"SomeInnerObject": {
"SomeProperty": {
"Id": "123",
"Type": "abc",
"Name": "some value"
}
}
}
I'm writing a C# class to store the data in memory in order to do some comparisons. The nesting of the JSON object causes the class to look annoyingly repetitive:
public class MyClass
{
public SomeObjectModel SomeObject { get; set; }
public class SomeObjectModel
{
public SomeInnerObjectModel InnerObject { get; set; }
public class SomeInnerObjectModel
{
// etc...
}
}
}
I know for sure that the inner classes, like "SomeObjectModel", are only going to be read from and not instantiated elsewhere, so is there a way to combine the class definition and property definition lines into something more like this?
public class MyClass
{
public SomeObject { get; set; } :
{
public SomeInnerObject { get; set; } :
{
// etc...
}
}
}
EDIT:
The JSON will have arrays in it, so take that into account if you are proposing an alternative using generics, etc.
If you're using this just to deserialize the JSON, you don't even need to define a class.. You can use a nested anonymous type.
First, create and populate (with dummy values) an anonymous type that has the properties you need to be able to read. Then pass it as the second parameter to DeserializeAnonymousType<T>(string,T).
The result is a new instance of the same anonymous type that you created, but now it's populated with values from the JSON.
var json = #"{'SomeObject': {'SomeInnerObject': {'SomeProperty': {'Id': '123','Type': 'abc','Name': 'some value'}}}}";
var template = new
{
SomeObject = new
{
SomeInnerObject = new
{
SomeProperty = new
{
Id = default(int),
Type = default(string),
Name = default(string)
}
}
}
};
var result = JsonConvert.DeserializeAnonymousType(json, template);
var id = result.SomeObject.SomeInnerObject.SomeProperty.Id;
var type = result.SomeObject.SomeInnerObject.SomeProperty.Type;
var name = result.SomeObject.SomeInnerObject.SomeProperty.Name;
Console.WriteLine("{0} {1} {2}", id, type, name);
Output:
123 abc some value
See my working example on DotNetFiddle.
Edit: If your JSON contains an array, you can use new[] {} to create an array based on type inference and then put the anonymous types inside, like this:
var json = #"{ 'SomeObjects': [ { 'Id': '123', 'Name': 'some value' }, { 'Id': '456', 'Name': 'another value' } ]}";
var template = new
{
SomeObjects = new [] { new { Id=default(int), Name=default(string)} }
};
The short answer is no, C# does not support any version of that syntactic sugar. The closest thing in C# is probably either anonymous types or value tuples.
Other languages have similar features:
C++ supports "inline classes" in declarations.
Java supports anonymous classes that are more sophisticated than C# anonymous record types.
Scala supports case classes which declare their members in the header of the declaration.
And so on. I think the first is the closest thing to what you are looking for.
Scala-style class declarations have been proposed for C# many times in the last decade, and may finally make it into C# 8; see https://blog.cdemi.io/whats-coming-in-c-8-0-records/
You can use the dynamic type. Here is a minimal example:
using Newtonsoft.Json;
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
dynamic x = JsonConvert.DeserializeObject("[{key: '1001', value: 'test'}, {key: '1002', value: 'test2'}, ]");
Console.WriteLine(x[0].key);
Console.WriteLine(x[0].value);
Console.WriteLine(x[1].key);
Console.WriteLine(x[1].value);
Console.ReadLine();
}
}
}
You can create classes by using the paste special, paste JSON as classes.
https://channel9.msdn.com/Series/Windows-Store-Developer-Solutions/Quckly-Generate-C-Classes-from-JSON-Responses#time=01m56s

json to C# objects

I receive a JSON string from a external system and I want to turn in to C# objects. The problem is that the "CAR" objects are unique in the string and I do not know how many there is (0 to many).
Now, It doesn't make sense to hardcode classes for each "CAR" like class: CAR1, class: CAR2 and so on. I tried to create a general CAR class and was thinking I could loop throu the JSON and create CAR objects.
The problem is that I cant't identify and find each CAR dynamically.I find the attributes and values for "CAR1" by hardcode "CAR1".
I would like to find a solution like:
foreach(item.key where key starts with "CAR")
Tried the following:
var expConverter = new ExpandoObjectConverter();
dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(jsondata, expConverter);
dynamic json = Newtonsoft.Json.JsonConvert.DeserializeObject(jsondata);
foreach (var itm in obj)
{
string s = itm.Key;
if(s.StartsWith("CAR"))
{
CarObj = new Car();
CarObj.HP = json.CAR1[0].HORSEPOWER[0];
CarObj.COLOR = json.CAR1[0].COLOR[0];
// Would like to use something like
// CarObj.HP = json.**item.key**[0].HORSEPOWER[0];
}
}
{
"CAR1": [{
"HORSEPOWER": ["180", "int", "Hp"],
"COLOR": ["GREEN", "string", "COLOR"]
}],
"CAR2": [{
"HORSEPOWER": ["200", "int", "Hp"],
"COLOR": ["BlUE", "string", "COLOR"]
}]
}
You're not forced to use dynamic right away. First use the strongly-typed APIs to find the right portion of the JSON, then switch to dynamic:
var obj = JObject.Parse(json);
foreach (var element in obj)
{
if (element.Key.StartsWith("CAR"))
{
dynamic value = element.Value;
var carObj = new Car();
carObj.HP = value[0].HORSEPOWER[0];
}
}
You can deserialize as Dictionary<string,List<CarDetails>>
public class CarDetails
{
public List<string> HORSEPOWER { get; set; }
public List<string> COLOR { get; set; }
}
and then iterate the dictionary to get the results.

How can I return my object property name as node name

I am stuck while returning api result, I have a class like
public partial class Sample
{
[JsonProperty("classificator")]
public List<Classificator> Classificator { get; set; }
}
public partial class Classificator
{
[JsonProperty("Value")]
public string Value { get; set; }
[JsonProperty("Description")]
public string Description { get; set; }
}
Let's say GetJson method retrieve our data from the database, there are 2 records and the data like
-- Value - Description
1- A - AXA
2- B - BXA
response = GetJson(); // this method gets data from db
return Content(HttpStatusCode.OK, response);
when I return this, it's like
{
"classificator": [{
"Value": "A",
"Description": "AXA"
}, {
"Value": "B",
"Description": "BXA"
}
]
}
but I would like to see like, I want to see bellowing result;
{
"classificator": [{
"A": "AXA"
}, {
"B" : "BXA"
}
]
}
I would like to ask you maybe someone knows a good practice or document(tutorial) about it.
I solve it by using, Dictionary < string, model >
but I need to return a huge nested field I cant implement this solution for all different nodes.
I solved by using Dictionary< string, object >
I put 2 nested objects inside of an object value. In my case it looked some complex I refactor and try to work through readable dictionary hierarchy.
basically for this example it something like below,
Dictionary<string, object> fooDict = new Dictionary<string, object>();
fooDict.Add("A", "AXA"); // in my case I put 2 nested object to value field
fooDict.Add("B", "BXA");
var serializedObject = JsonConvert.SerializeObject(fooDict);
using (System.IO.StreamWriter file = new System.IO.StreamWriter(#"C:\xxx\result.txt", true))
{
file.WriteLine(serializedObject);
}

Categories