I have the following JSON array, that can also hold null values for some of the elements in the var array. The elements in the var array are always fix.
"vars": [
{
"varCol": [
{
"visible": false,
"val": 1,
},
{
"visible": false,
"val": 5,
},
{
"visible": false,
"val": 5,
},
null
],
"Type": "Type1"
"UniqueId": "ID1"
},
{
"varCol": [
{
"visible": true,
"val": 1,
},
null,
{
"visible": false,
"val": 5,
},
null
],
"Type": "Type2",
"UniqueId": "ID2"
}
]
I have the following C# deserializer classes:
public class Var
{
public int VarId { get; set; }
public string Type { get; set; }
public List<VarCol> VarCols { get; set; }
}
public class VarCol
{
public int VarColId { get; set; }
public bool Visible { get; set; }
public float Val { get; set; }
}
My desired output here is to have an entry in the VarCol that always holds the fixed structure of values in the array. In this case 4 entries in the varCol array for each vars element.
For the deserialization of the JSON I am using:
Var v = JToken.Parse(json_string).ToObject<Var>();
I wanted to insert this as a comment but it is too long so I have to post it as an answer.
Pay attention to commas because your JSON is not valid. I think you want to have something like this:
{
"vars": [
{
"varCol": [
{
"visible": false,
"val": 1
},
{
"visible": false,
"val": 5
},
{
"visible": false,
"val": 5
},
null
],
"Type": "Type1",
"UniqueId": "ID1"
},
{
"varCol": [
{
"visible": true,
"val": 1
},
null,
{
"visible": false,
"val": 5
},
null
],
"Type": "Type2",
"UniqueId": "ID2"
}
]
}
Have a nice day,
Alberto
Not sure what are you trying to achieve, because JToken.Parse() handles null values pretty much the same way as any other object value, so varCol will always have 4 elements.
Here's some test code: http://dotnetfiddle.net/9gGdH9
Sorry, my question was misleading.
The problem I was facing was saving to the DB through Entity Framework a null VarCol in the Var collection. I needed this to be able to know which of the elements in the collection is null, since I have a fixed size list.I solved this by instantiating the null VarCol with an empty VarCol as also Alex Skalozub indicated in one of his comments:
It depends on the way your object is stored in the database. If VarCol
has a many-to-one relation with Var, EF will probably store reference
to Var record in VarCol table. But null record cannot be saved to
database. That's why reading back will bring only those records that
were saved. You probably need to do some post-processing after parsing
JSON to replace null values with empty records before saving to DB. –
Alex Skalozub 5 hours ago
Related
I'm working with an external API to get some product information, the end points return some data in a static structure and others in dynamic depending on the product I'm inquiring.
For example if I'm requesting data for a soap I get the following JSON:
{ "id": 4623,
"brand": "Fa",
"category": "Cleansing/Washing/Soap – Body",
"photos": {
"name": "Photos",
"value": [ "https//test.com/1jpg"
]
},
"productname": {
"name": "Product Name",
"value": "Fa Shower Cream Yoghurt Vanilla Honey"
},
"warningstatement": {
"name": "Warning Statement",
"value": "Avoid contact with eyes."
},
"consumerusageinstructions": {
"name": "Consumer Usage Instructions",
"value": "Apply directly on skin."
}
and if I'm inquiring about a cheese I get the following JSON:
{
"id": 10838,
"brand": "Domty",
"category": "Cheese",
"photos": {
"name": "Photos",
"value": [ "https://test.com/2.jpg"
]
},
"productname": {
"name": "Product Name",
"value": "Domty White Low Salt Cheese"
},
"description": {
"name": "1312",
"value": "Highest premium quality"
},
"netcontent": {
"name": "Net Content",
"value": "900 gm"
}
and it goes on for every product they offer. I've no problem deserializing the static data like photos array, product name, brand, and id since they are applicable to every product, but the other dynamic properties are the ones I'm concerned about. Is there a way to deserialize to a class like this:
public class Info {
property string key { get; set;} // to hold description, consumerusageinstructions or what every key
property string name { get; set;}
property string value { get; set;}
}
and then add a collection of the class info to my product model?
One way is just to parse the Json and look at the actual entities: this example uses Json.Net:
var parsed = JObject.Parse(json);
var properties = parsed.Children().Cast<JProperty>();
foreach (var property in properties) {
// an alternative here would be to just have a list of names to ignore
if (!(property.Value is JObject jObject)) {
// skip the simple property/value pairs
continue;
}
if (property.Name == "productname") {
// skip product name
continue;
}
if (property.Value["value"] is JArray) {
// skip photos
continue;
}
// Add to ProductModel instance
Console.WriteLine($"{property.Name} => {property.Value["name"]} = {property.Value["value"]}");
}
Outputs:
warningstatement => Warning Statement = Avoid contact with eyes.
consumerusageinstructions => Consumer Usage Instructions = Apply directly on skin.
description => 1312 = Highest premium quality
netcontent => Net Content = 900 gm
I hope it doesn't seem like I want to be spoon-fed at this rate of asking questions, but this next question is a bit more difficult and I don't understand how to solve this at all.
{
"results": [
{
"id": "5d914028302b840050acbe62",
"picture": "https://utellyassets9-1.imgix.net/api/Images/4e4d50a0040fd4500193202edbafce6a/Redirect",
"name": "BoJack Horseman",
"locations": [
{
"icon": "https://utellyassets7.imgix.net/locations_icons/utelly/black_new/NetflixIVAUS.png?w=92&auto=compress&app_version=ae3576e2-0796-4eda-b953-80cadc8e2619_eww2020-05-08",
"display_name": "Netflix",
"name": "NetflixIVAUS",
"id": "5d81fe2fd51bef0f42268f0f",
"url": "https://www.netflix.com/title/70298933"
}
],
"provider": "iva",
"weight": 5654,
"external_ids": {
"iva_rating": null,
"imdb": {
"url": "https://www.imdb.com/title/tt3398228",
"id": "tt3398228"
},
"tmdb": {
"url": "https://www.themoviedb.org/movie/61222",
"id": "61222"
},
"wiki_data": {
"url": "https://www.wikidata.org/wiki/Q17733404",
"id": "Q17733404"
},
"iva": {
"id": "783721"
},
"gracenote": null,
"rotten_tomatoes": null,
"facebook": null
}
},
{
"id": "5e2ce07890c0e033a487e3d2",
"picture": "https://utellyassets9-1.imgix.net/api/Images/326d2853ff6885c41b9adb05278017f6/Redirect",
"name": "Dragon Ball Z: Bojack Unbound",
"locations": [
{
"icon": "https://utellyassets7.imgix.net/locations_icons/utelly/black_new/iTunesIVAUS.png?w=92&auto=compress&app_version=ae3576e2-0796-4eda-b953-80cadc8e2619_eww2020-05-08",
"display_name": "iTunes",
"name": "iTunesIVAUS",
"id": "5d80a9a5d51bef861d3740d3",
"url": "https://itunes.apple.com/us/movie/dragon-ball-z-bojack-unbound-subtitled-original-version/id1381102560"
}
],
"provider": "iva",
"weight": 0,
"external_ids": {
"iva_rating": null,
"imdb": {
"url": "https://www.imdb.com/title/tt0142238",
"id": "tt0142238"
},
"tmdb": {
"url": "https://www.themoviedb.org/movie/39105",
"id": "39105"
},
"wiki_data": {
"url": "https://www.wikidata.org/wiki/Q1255010",
"id": "Q1255010"
},
"iva": {
"id": "406043"
},
"gracenote": null,
"rotten_tomatoes": null,
"facebook": null
}
}
],
"updated": "2020-05-08T05:19:01+0100",
"term": "Bojack",
"status_code": 200,
"variant": "ivafull"
}
Alright, so the first 0: represents the option that the API returned (so in this case it's bojack horseman) and any following number (if it said 1: afterward) would be a different result.
I tried writing a second class to deal with results (resultoverall) that is controlled by the overall,
public class overall
{
[JsonProperty("status_code")]
public string status_code { get; set; }
[JsonProperty("term")]
public string term { get; set; }
[JsonProperty("updated")]
public string updated { get; set; }
[JsonProperty("results")]
public List<resultoverall> results { get; set; }
}
public partial class resultoverall
{
[JsonProperty("name")]
public string name { get; set; }
}
and tried
overall BestGamer = JsonConvert.DeserializeObject<overall>(thicky);
List<overall> ObjOrderList = JsonConvert.DeserializeObject<List<overall>>(thicky);
to be able to access data from both outside of the 0: and inside of the 0: (BestGamer / overall handles outside, resultoverall / Tapioca handles inside, but I get error about the type requiring a JSON array
even though it is already in an array format.
How do I set this up to access data inside of the 0:, for each number that appears (so if there is 2 options I can access info from both?
Using Console.WriteLine(BestGamer.updated); correctly gives me the info from the overall group, but using the Console.WriteLine(BestGamer.results.name); does not work, saying:
Error CS1061 'extension method 'name' accepting a first argument of type 'List<resultoverall>' could be found (are you missing a using directive or an assembly reference?)
You have a JSON object, not an array. Therefore you can't deserialize it as an array. That is correct. You are already deserializing it to an object. So after
overall BestGamer = JsonConvert.DeserializeObject<overall>(thicky);
You can access the objects in results like
var numberOfResults = BestGamer.results.Count;
var firstResult = BestGamer.results[0];
var firstResultsName = BestGamer.results[0].name;
// or loop through the results
foreach (var item in BestGamer.results)
{
// item.Name gives you the name of the current result
}
I have a class which has multiple variables that are all correctly created via a JSON text file except for one list of enums which belong to an Effects class
the below class always has the enums in the effectedBy list defaulted to element 0
[System.Serializable]
public class InteractableObject : Item, IEffectReceiver
{
protected List<Stats> stats = new List<Stats>();
protected HashSet<Effects> currentEffects;
public List<Effects.EffectType> effectedBy = new List<Effects.EffectType>();
public GameObject destroyedPrefab;
private bool destructible = true;
private bool destroyed = false;
this is the class that holds the enum I am trying to set
[System.Serializable]
public class Effects
{
public bool usingEffect = false;
public int effectOverTimeChance = 0;
public EffectType effectType;
public Stats.StatType effectedStat = Stats.StatType.health;
public int minAmount;
public int maxAmount;
public enum EffectType
{
bleed,
electric,
fire,
heal,
physical,
poison
}
this is my JSON text this all works fine apart from that one enum effectedBy on the last entry
{
"meleeWeapons": [
{
"id": 0,
"prefabID": "claw",
"title": "claw",
"description": "razor sharp claws",
"slotType": 4,
"effects": [
{
"effectType": 4,
"minAmount": 2,
"maxAmount": 4
}
]
},
{
"id": 1,
"title": "hammer",
"description": "heavy and intimidating",
"slotType": 4
}
],
"interactableObjects": [
{
"id": 2,
"prefabID": "BasicCrate",
"title": "crate",
"effectedBy": [
{
"effectType": 4
}
]
}
]
}
I can work around this by changing effectedBy to a list of effects and giving the effect itself the enum value I require but if I could I would rather just keep it as the list of effectTypes.
Anybody have any insight into this?
public List<Effects.EffectType> effectedBy = new List<Effects.EffectType>();
is the main issue. That code says that you have a list (or array) of values of the type EffectType. Unfortunately, that is not the shape of your JSON:
"effectedBy": [
{
"effectType": 4
}
]
That is an not an array of EffectType. It is an array of objects with an effectType property of type EffectType.
Thus you should instead use:
public List<EffectTypeWrapper> effectedBy = new List<EffectTypeWrapper>();
where EffectTypeWrapper is defined as:
public class EffectTypeWrapper
{
public EffectType effectType { get; set; }
}
In future, I would strongly encourage use of https://app.quicktype.io/#l=cs&r=json2csharp (example https://app.quicktype.io?share=4jOYgCbQhD7tmaJf10Ln).
I tried creating a new interactableObject and just setting the effectedBy value then wrote that to JSON which gave the following.
{
"id": "",
"prefabID": "",
"title": "",
"description": "",
"itemPrefab": {
"instanceID": 0
},
"myContainer": {
"instanceID": 0
},
"health": {
"current": 0.0,
"minimum": 0.0,
"maximum": 0.0,
"rechargeRate": 0.0,
"rechargeDelay": 0.0,
"statType": 0
},
"effectedBy": [
2
],
"type": 0,
"destroyedPrefab": {
"instanceID": 0
}
}
Creating my interactableObject with my text file and assigning the enums without the effectType variable and just writing the enum value works as i was originally intending it to
"interactableObjects": [
{
"id": 2,
"prefabID": "BasicCrate",
"title": "crate",
"effectedBy": [
2,
4
],
}
]
I'm calling an API endpoint that return me a JSON some data and I need to deserialize it into classes and nested classes that may contains ReactiveProperty fields ( a type coming from UniRx library which is a reimplemntation of Reactive extensions for Unity3D ).
I'm new to C#, I tried some things, but I can't achieve it the way i want.
Here is the json returned by my Api ( in reality there are more data but this example will suffice ) :
"user": {
"id": "87f2ae6e-af99-4f8e-9d69-08de6ad6baf8",
"username": "test",
"email": "test#test.com",
"money": 800,
"morale": 100,
"health": 100,
"credits": 15,
"energy": 100,
"banned_at": null,
"last_connection": null,
"officers": [
{
"id": "2b72d9d4-635c-4b32-9575-5df49f566e93",
"name": "David Le Salmon",
"is_available": true,
"age": 42,
"condition": 100,
"user_id": "87f2ae6e-af99-4f8e-9d69-08de6ad6baf8"
},
{
"id": "ebc4074c-7b94-4ea3-96d9-f80608972afa",
"name": "Philippe Mercier",
"is_available": true,
"age": 34,
"condition": 100,
"user_id": "87f2ae6e-af99-4f8e-9d69-08de6ad6baf8"
},
{
"id": "edba67b5-9053-4fd6-b64e-6b85f4d0cc25",
"name": "Raymond Wagner-Berthelot",
"is_available": true,
"age": 55,
"condition": 100,
"user_id": "87f2ae6e-af99-4f8e-9d69-08de6ad6baf8"
}
],
"vehicles": [
{
"id": "3161d274-ed2a-491b-8515-beb7da9bfd29",
"mileage": 0,
"health": 100,
"level": 3,
"equipment_level": 4,
"vehicle_prefab_id": "14e8d96f-0e85-40ad-b01b-5f00a37b1108"
},
{
"id": "ff984c79-4511-4ade-92d1-9bf6899a243c",
"mileage": 0,
"health": 100,
"level": 4,
"equipment_level": 4,
"vehicle_prefab_id": "14e8d96f-0e85-40ad-b01b-5f00a37b1108"
}
]
}
}
I defined some classes like this :
[Serializable]
public class AppState {
public User user = new User();
}
[Serializable]
public class User {
public string username;
public string email;
public ReactiveProperty<int> money = new ReactiveProperty<int>();
public ReactiveProperty<int> morale = new ReactiveProperty<int>();
public ReactiveProperty<int> health = new ReactiveProperty<int>();
public ReactiveProperty<int> energy = new ReactiveProperty<int>();
public List<Officer> officers = new List<Officer[]>();
public List<Vehicles> vehicles = new List<Vehicle>();
}
Officer and Vehicle are defined in the same way, some fields are ReactiveProperty, some not.
And here how I tried to deserialize :
RestClient.Get("/getAppState").Then(response => {
var stuff = JsonConvert.DeserializeObject<AppState>(response.Text);
})
This code throws this error :
Could not cast or convert from System.String to UniRx.ReactiveProperty
I found something that "work", I assigned all ReactiveProperty fields manually, but it's really tedious.
Is there a way to do this in C#?
Make the Reactive property fields NonSerializable.
Make new serializable String fields.
Deserialize the JSON.
Add some functions to your class(es) that cast the string representations into your ReactiveProperty variables.
Call those functions after you desierialize your JSON.
Note: I am unfamiliar with ReactiveProperty, you'll need to investigate that library to find the best way of casting a string into those objects.
I've got a JSON stream coming back from a server, and I need to search for a specific value of the node "ID" using JSON.net to parse the data.
And I can almost make it work, but not quite because the results coming back are deeply nested in each other -- this is due to the fact that I'm getting a folder structure back. I've boiled the JSON down to a much simpler version. I'm getting this:
{
"data": {
"id": 0,
"name": "",
"childFolders": [{
"id": 19002,
"name": "Locker",
"childFolders": [{
"id": 19003,
"name": "Folder1",
"childFolders": [],
"childComponents": [{
"id": 19005,
"name": "route1",
"state": "STOPPED",
"type": "ROUTE"
}]
}, {
"id": 19004,
"name": "Folder2",
"childFolders": [],
"childComponents": [{
"id": 19008,
"name": "comm1",
"state": "STOPPED",
"type": "COMMUNICATION_POINT"
}, {
"id": 19006,
"name": "route2",
"state": "STOPPED",
"type": "ROUTE"
}, {
"id": 19007,
"name": "route3",
"state": "STOPPED",
"type": "ROUTE"
}]
}],
"childComponents": []
}],
"childComponents": []
},
"error": null
}
I can almost get there by going:
var objects = JObject.Parse(results);
var subobjects = objects["data"]["childFolders"][0]["childFolders"][1];
I can see in the debug view that it'll parse the object, but won't let me search within.
My ultimate goal is to be able to search for "route3" and get back 19007, since that's the ID for that route. I've found some results, but all of them assume you know how far nested the object is. The object I'm searching for could be 2 deep or 20 deep.
My ultimate goal is to be able to search for "route3" and get back 19007
You can use linq and Descendants method of JObject to do it:
var dirs = JObject.Parse(json)
.Descendants()
.Where(x=>x is JObject)
.Where(x=>x["id"]!=null && x["name"]!=null)
.Select(x =>new { ID= (int)x["id"], Name = (string)x["name"] })
.ToList();
var id = dirs.Find(x => x.Name == "route3").ID;
You can use the SelectToken or SelectTokens functions to provide a JPath to search for your desired node. Here is an example that would provide you the route based on name:
JObject.Parse(jsonData)["data"].SelectToken("$..childComponents[?(#.name=='route3')]")
You can find more documentation on JPath here
Simply write a recursive function:
private Thing FindThing(Thing thing, string name)
{
if (thing.name == name)
return thing;
foreach (var subThing in thing.childFolders.Concat(thing.childComponents))
{
var foundSub = FindThing(subThing, name);
if (foundSub != null)
return foundSub;
}
return null;
}
class RootObject
{
public Thing data { get; set; }
}
class Thing
{
public int id { get; set; }
public string name { get; set; }
public List<Thing> childFolders { get; set; } = new List<Thing>();
public List<Thing> childComponents { get; set; } = new List<Thing>();
}
And using it:
var obj = JsonConvert.DeserializeObject<RootObject>(jsonString);
var result = FindThing(obj.data, "route3");