question

jeff0rosenberg avatar image
jeff0rosenberg asked

Deserialize nested Json ExecuteCloudScriptResult Dictionary

How do I deserialize an ExecuteCloudScriptResult.FunctionResult with a nested Dictionary<string, string>?

Here is the CloudScript generating the result:

// Equip an item to the player
// Args:
//    itemInstanceId             string         The itemInstanceId of the item to equip
//    slot (optional)            string         Which slot to equip to. Either "Left Hand", "Right Hand". Only used by 1H weapons, other items use their default slot while 2H weapons equip to both hand slots.
handlers.EquipItem = function(args) {
    var inventory = GetPlayerInventory(currentPlayerId);
    var item = FindItemByInstanceId(inventory, args.itemInstanceId);
    var data = GetPlayerReadOnlyData(currentPlayerId, ["Equipment"]); 
    var equipment = JSON.parse(data["Equipment"].Value);
    if(!EquipValid(item, args.slot)) {  // Method checks if the item and slot are valid
        log.debug("Equip is not valid for this object");
        return {
            Success: false,
            Result: equipment // equipment is returned without modification, so even in the case of a failure the player gets a result
        };
    } 
    else {
        // Modify the equipment object
        data["Equipment"] = JSON.stringify(equipment);
        UpdatePlayerReadOnlyData(currentPlayerId, data); // Method to apply the data object back to the player's ReadOnlyData
        return {
            Success: true,
            Result: equipment
        };
    }
}

Here's a sample result:

{
    "Success":true,
    "Result": {
        "Head":"",
        "Neck":"",
        "Shoulders":"",
        "Arms":"",
        "Hands":"",
        "Torso":"",
        "Chest":"",
        "Back":"",
        "Waist":"",
        "Legs":"",
        "Thighs":"",
        "Feet":"2037797F861EA380",
        "Right Hand":"",
        "Left Hand":"1DEEFACB07A59DA9"
    }
}

Each Key in Result is the slot, while the Value is the ItemInstanceID of the item. Here the action is a success and we see that the Feet slot and Left Hand slot contain items. Likely I'll also include more information about the equip or more error logging.

My player's Equipment (client-side, C#) is a Dictionary<string, string>. I can then use the slot's Value to look up an item from the player's loaded Inventory (List<ItemInstance>) or from the catalog (List<CatalogItem>).

I've tried this, which works except for the nested dictionary:

private class EquipItemFunctionResult
{
    public bool Success;
    public Dictionary<string, string> Equipment;
}

public void EquipItem(string itemInstanceId, string itemSlot = "")
{
    var request = new ExecuteCloudScriptRequest()
    {
        FunctionName = "EquipItem",
        FunctionParameter = new {
                itemInstanceId = itemInstanceId,
                slot = itemSlot
            }
    };
    System.Action<ExecuteCloudScriptResult> resultCallback = delegate(ExecuteCloudScriptResult obj)
    {
            var parsedResult = JsonUtility.FromJson<EquipItemFunctionResult>(obj.FunctionResult.ToString());
            Debug.Log(parsedResult.Success);
            Debug.Log(parsedResult.Equipment);
    };
    System.Action<PlayFabError> errorCallback = delegate(PlayFabError obj)
    {
        Toolbox.DialogWindow.ShowPlayFabError(obj);
    };
    PlayFabClientAPI.ExecuteCloudScript(request, resultCallback, errorCallback);
}

For a valid equip, this results in

True


Null
CloudScript
2 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

jeff0rosenberg avatar image jeff0rosenberg commented ·

I need to add that in the above I'm using Unity's built in JsonUtility which wouldn't work anyways. I've also tried PlayFab Json:

PlayFab.Json.JsonWrapper.DeserializeObject<EquipItemFunctionResult>(obj.FunctionResult.ToString());

and Newtonsoft Json.Net:

Newtonsoft.Json.JsonConvert.DeserializeObject<EquipItemFunctionResult>(obj.FunctionResult.ToString());

But these both result in a valid Success and null Result.

0 Likes 0 ·
jeff0rosenberg avatar image jeff0rosenberg commented ·

Feels kind of lame to answer my own question so quickly, but I'd like to share a solution though I don't think it's the best. Instead of deserializing the whole FunctionResult into one object, deserialize it into a Dictionary<string, object> and then further deserialize/convert where necessary:

var parsedResult = PlayFab.Json.JsonWrapper.DeserializeObject<Dictionary<string, object>>(obj.FunctionResult.ToString());
Equipment = PlayFab.Json.JsonWrapper.DeserializeObject<Dictionary<string, string>>(parsedResult["Result"].ToString());

I don't particularly like this solution, I'd rather deserialize into a class or struct like I've see used elsewhere so I could avoid needing to drill into the Json myself at every step, access variables directly instead of by string keys, and not have to convert the root objects.

0 Likes 0 ·

1 Answer

·
brendan avatar image
brendan answered

We actually made a change to the most recent update (Jan 30th) to the Unity SDK which improved the JSON wrapper support for complex objects like this. Could you try updating, to see if the SimplyJson route (the one we wrap) works for you?

2 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

jeff0rosenberg avatar image jeff0rosenberg commented ·

Ah, I found the problem. My object had a dictionary named Equipment, but I was returning a dictionary named Result. It works now, and may have worked prior as well. Oops.

0 Likes 0 ·
brendan avatar image brendan jeff0rosenberg commented ·

No worries! Glad you found the issue.

0 Likes 0 ·

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.