0

I am currently working on pulling data from the Bungie API Manifest. I successfully retrieve the .json file and i am the stage of trying to extract what i want from the file.

I'm trying to loop through all the entries in the JSON file and add to list where both activity.Key & activity.displayProperties.name exist as they do in the below example

"2693136600": {
    "displayProperties": {
    "description": "\"Grow fat from strength.\"",
    "name": "Leviathan: Normal",
    "icon": "/common/destiny2_content/icons/8b1bfd1c1ce1cab51d23c78235a6e067.png",
    "hasIcon": true
    },

Full Response for the above specific JSON Key:

"2693136600": { 
"displayProperties": { 
"description": "\"Grow fat from strength.\"", 
"name": "Leviathan: Normal", 
"icon": "/common/destiny2_content/icons/8b1bfd1c1ce1cab51d23c78235a6e067.png", 
"hasIcon": true 
}, 
"originalDisplayProperties": { 
"description": "\"Grow fat from strength.\"", 
"name": "Leviathan", 
"icon": "/img/misc/missing_icon_d2.png", 
"hasIcon": false 
}, 
"selectionScreenDisplayProperties": { 
"description": "Accept an invitation from the Cabal emperor to prove yourself aboard the Leviathan.\n\nRaids are 6 player cooperative activities which test the limits of your skill and teamwork. Master the unique mechanics of each encounter to succeed and earn powerful and exclusive rewards.", 
"name": "Normal", 
"hasIcon": false 
}, 
"releaseIcon": "/img/misc/missing_icon_d2.png", 
"releaseTime": 0, 
"activityLevel": 30, 
"completionUnlockHash": 0, 
"activityLightLevel": 750, 
"destinationHash": 3093898507, 
"placeHash": 330251492, 
"activityTypeHash": 2043403989, 
"tier": 0, 
"pgcrImage": "/img/destiny_content/pgcr/raid_gluttony.jpg", 
"rewards": [ 
{ 
"rewardItems": [ 
{ 
"itemHash": 2127149322, 
"quantity": 0 
} 
] 
}, 
{ 
"rewardItems": [ 
{ 
"itemHash": 669267835, 
"quantity": 0 
} 
] 
} 
], 
"modifiers": [ 
{ 
"activityModifierHash": 2863316929 
}, 
{ 
"activityModifierHash": 3296085675 
}, 
{ 
"activityModifierHash": 871205855 
}, 
{ 
"activityModifierHash": 2770077977 
} 
], 
"isPlaylist": false, 
"challenges": [], 
"optionalUnlockStrings": [], 
"inheritFromFreeRoam": false, 
"suppressOtherRewards": false, 
"playlistItems": [], 
"matchmaking": { 
"isMatchmade": false, 
"minParty": 1, 
"maxParty": 6, 
"maxPlayers": 6, 
"requiresGuardianOath": false 
}, 
"directActivityModeHash": 2043403989, 
"directActivityModeType": 4, 
"activityModeHashes": [ 
2043403989, 
1164760493 
], 
"activityModeTypes": [ 
4, 
7 
], 
"isPvP": false, 
"insertionPoints": [ 
{ 
"phaseHash": 2188993306, 
"unlockHash": 0 
}, 
{ 
"phaseHash": 1431486395, 
"unlockHash": 0 
}, 
{ 
"phaseHash": 3847906370, 
"unlockHash": 0 
}, 
{ 
"phaseHash": 4231923662, 
"unlockHash": 0 
} 
], 
"activityLocationMappings": [], 
"hash": 2693136600, 
"index": 559, 
"redacted": false, 
"blacklisted": false 
},

My issue is that displayProperties doesn't always contain name so when ever this occurs the code fails.

I've tried various ways of doing this i can't get anything to work as they all it the catch

I am out of my depth with this one but it's almost the last piece in the puzzle for what i'm building so any help will be greatly appreciated.

Below is my latest failed attempt

HttpResponseMessage response = await client.GetAsync("https://bungie.net/common/destiny2_content/json/en/DestinyActivityDefinition-7ab91c74-e8a4-40c7-9f70-16a4354125c0.json");

    if (response.IsSuccessStatusCode)
    {
       try
       {
           dynamic content = response.Content.ReadAsAsync<ExpandoObject>().Result;                   

           foreach (dynamic activity in content)
           {
           //check if .name field exists under displayProperties
               bool exist = activity.displayProperties.name;
               if (exist == false)
               {
               //do nothing
               }
               else
               {
               //add both Key and displayProperties.name to list
                   bungieActivities.Add(new AllBungieActivities() { ActivityHash = activity.Key, ActivityDescription = activity.displayProperties.name });
               }                        
            }  
         }
         catch
         {
           throw new ArgumentException("The member could not be found.");
         }
      }

Update I've not managed to implement any of the below answers and get it to work, i have however manager to get the below to work but takes quite a bit of time to pass this part

List<AllBungieActivities> bungieActivities = new List<AllBungieActivities>();

            var response = await client.GetAsync("https://bungie.net/common/destiny2_content/json/en/DestinyActivityDefinition-7ab91c74-e8a4-40c7-9f70-16a4354125c0.json");

            if (response.IsSuccessStatusCode)
            {
                try
                {
                    var content = await response.Content.ReadAsStringAsync();
                    JObject activity = JObject.Parse(content);

                    foreach (JObject refId in activity.Properties().Select(p => p.Value["displayProperties"]))
                    {
                        if (refId.ContainsKey("name"))
                        {
                            bungieActivities.Add(new AllBungieActivities() { ActivityName = (string)refId["name"], ActivityHash = refId.Path.Substring(0, refId.Path.LastIndexOf(".") + 0) });
                        }
                    }

                }
                catch
                {
                    throw new ArgumentException("The member could not be found.");
                }
            }

public class AllBungieActivities
    {
        public string ActivityName;
        public string ActivityHash;
    }

Danny
  • 59
  • 10
  • Possible duplicate of [How do I check if a property exists on a dynamic anonymous type in c#?](https://stackoverflow.com/questions/9956648/how-do-i-check-if-a-property-exists-on-a-dynamic-anonymous-type-in-c) – devNull Nov 25 '19 at 22:52
  • As answers have already been added, I thought I'd point out that you're using `.Result` in what seems to already be an asynchronous context. Use `await response.Content.ReadAsAsync();` to prevent blocking or deadlocking your application. – ColinM Nov 25 '19 at 23:00

3 Answers3

2

Instead of deserializing to ExpandoObject, create an actual model that represents your data and use that. If you don't have the necessary keys in your json, your model will be null and then you can just do a null check.

public class Activity
{
    public string Key {get;set;}
    public DisplayProperties DisplayProperties {get;set;}
}

public class DisplayProperties
{
   public string Description {get;set;}
   public string Name {get;set;}
   public string Icon {get;set;}
   public bool HasIcon {get;set;}
}


var activity= response.Content.ReadAsAsync<Activity>().Result;

if(!string.IsNullOrWhiteSpace(activity.Key) && !string.IsNullOrWhiteSpace(activity.DisplayProperties?.Name)
//add to list
JohanP
  • 5,252
  • 2
  • 24
  • 34
  • The massive benefit here of course being the strong typing and compile time safety. – ColinM Nov 25 '19 at 22:59
  • Thanks, this seems the most sensible way of doing it. I am however facing 2 issues. 1: its always returning Null and it wont allow for me to use a foreach statement as there isn't an public IEnumarable in the Activity Class – Danny Nov 26 '19 at 07:50
  • @Danny Your very short snippet of json is insufficient to determine the entire model. Please update your question to show minimal json that will work. – JohanP Nov 26 '19 at 08:03
  • @JohanP - let me know if there is anything else u need – Danny Nov 26 '19 at 14:02
  • @Danny Please update your question with the contents, not an external link. – ColinM Nov 26 '19 at 14:04
  • @Danny do you have a full, valid JSON sample? Run it through an online JSON Validator to confirm it's correct. – ColinM Nov 26 '19 at 14:15
  • @ColinM - The full JSON is taken from https://www.bungie.net/common/destiny2_content/json/en/DestinyActivityDefinition-7ab91c74-e8a4-40c7-9f70-16a4354125c0.json – Danny Nov 26 '19 at 14:44
0

You can cast the ExpandoObject to a dictionary:

HttpResponseMessage response = await client.GetAsync("https://bungie.net/common/destiny2_content/json/en/DestinyActivityDefinition-7ab91c74-e8a4-40c7-9f70-16a4354125c0.json");

if (response.IsSuccessStatusCode)
{
   try
   {
       dynamic content = response.Content.ReadAsAsync<ExpandoObject>().Result;                   

       foreach (dynamic activity in content)
       {
           var dict = ((IDictionary<String, object>)activity.displayProperties)
           //check if .name field exists under displayProperties
           bool exist = dict.ContainsKey("name");
           if (exist == false)
           {
           //do nothing
           }
           else
           {
           //add both Key and displayProperties.name to list
               bungieActivities.Add(new AllBungieActivities() { ActivityHash = activity.Key, ActivityDescription = activity.displayProperties.name });
           }                        
        }  
     }
     catch
     {
       throw new ArgumentException("The member could not be found.");
     }
  }
Kenneth
  • 28,294
  • 6
  • 61
  • 84
0

There are similar questions here on StackOverflow. This is one of the better answers I found: https://stackoverflow.com/a/2839629/12431728

According to that answer (which references MSDN) you can use an ExpandoObject, which implements IDictionary:

public sealed class ExpandoObject : IDynamicMetaObjectProvider, 
    IDictionary<string, Object>, ICollection<KeyValuePair<string, Object>>, 
    IEnumerable<KeyValuePair<string, Object>>, IEnumerable, INotifyPropertyChanged

And that answer's suggested solution:

var expandoObject = ...;
if(((IDictionary<String, object>)expandoObject).ContainsKey("SomeMember")) {
    // expandoObject.SomeMember exists.
}

You can find more about ExpandoObject here: https://learn.microsoft.com/en-us/dotnet/api/system.dynamic.expandoobject?view=netframework-4.8

Matt U
  • 4,970
  • 9
  • 28