2

Normally when I use API's and get a Json string back I simply make a class to suit the string and populate that class using newton JsonConvert.DeserializeObject.

However I now have a Json string which 1 of the fields does not have a name.

{
    "attacks": {
        "114862720": {
            "code": "115dc2b990153c41c33d519b26cc302a",
            "timestamp_started": 1596782220,
            "timestamp_ended": 1596782226,
            "attacker_id": 580816,
            "attacker_name": "chedders",
            "attacker_faction": 32585,
            "attacker_factionname": "Heart of a Pirate",
            "defender_id": 65306,
            "defender_name": "-Clansdancer",
            "defender_faction": 0,
            "defender_factionname": null,
            "result": "Attacked",
            "stealthed": 0,
            "respect_gain": 4.14,
            "chain": 3,
            "modifiers": {
                "fairFight": 3,
                "war": 1,
                "retaliation": 1,
                "groupAttack": 1,
                "overseas": 1,
                "chainBonus": 1
            }
        },
        "114862829": {
            "code": "8bf08c8ceb9b72f05f40235310cd822e",
            "timestamp_started": 1596782339,
            "timestamp_ended": 1596782344,
            "attacker_id": 580816,
            "attacker_name": "chedders",
            "attacker_faction": 32585,
            "attacker_factionname": "Heart of a Pirate",
            "defender_id": 964979,
            "defender_name": "brko21",
            "defender_faction": 0,
            "defender_factionname": null,
            "result": "Attacked",
            "stealthed": 0,
            "respect_gain": 4.11,
            "chain": 4,
            "modifiers": {
                "fairFight": 3,
                "war": 1,
                "retaliation": 1,
                "groupAttack": 1,
                "overseas": 1,
                "chainBonus": 1
            }
        }
    }
}

After attacks is an ID which is unique to each entry.so building a class for this as I normally would just wont work as the ID is unknown.

Any pointers on how to deserialise this string would be most welcome.

5
  • Looks like your model is be a dictionary, where the key is a string, except it's not because dictionaries should use square brackets. What program created this output, and can you change it to be a dictionary? Commented Aug 23, 2020 at 7:25
  • This should work with dictionary: public IDictionary<string, MyModel> Attacks { get; set; } Commented Aug 23, 2020 at 7:35
  • 1
    This would deserialise to a Dictionary<string, MyType>. Where MyType is a type that you define with all of the properties within each object, in the JSON document. Commented Aug 23, 2020 at 7:36
  • I have no control over the data, it is from game API Commented Aug 23, 2020 at 7:42
  • Yes but the API follows a convention right? i.e you can expect each root node under "attacks" to have the same fields? Commented Aug 23, 2020 at 7:48

3 Answers 3

1

You can use the [JsonExtensionData] property.

Here's the official example:

public class DirectoryAccount
{
    // normal deserialization
    public string DisplayName { get; set; }

    // these properties are set in OnDeserialized
    public string UserName { get; set; }
    public string Domain { get; set; }

    [JsonExtensionData]
    private IDictionary<string, JToken> _additionalData;

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
        // SAMAccountName is not deserialized to any property
        // and so it is added to the extension data dictionary
        string samAccountName = (string)_additionalData["SAMAccountName"];

        Domain = samAccountName.Split('\\')[0];
        UserName = samAccountName.Split('\\')[1];
    }

    public DirectoryAccount()
    {
        _additionalData = new Dictionary<string, JToken>();
    }
}

So in your OnDeserializing method, you can get the values from the dictionary and add them to a proper field/cast them to a list of objects, etc.

Sign up to request clarification or add additional context in comments.

1 Comment

While this might be a good idea for other cases, within this case, I would not recommend this as the problem is not that the fields don't have names but rather the JSON document doesn't follow an exact schema. However, there is a pattern, its only the root node of each object that changes thus every other member can be automatically be deserialized.
1

This would deserialise to a Dictionary<string, Attack>. Where Attack is a type that you define with all of the properties within each object, in the JSON document.

This example assumes you're using NewtonSofts JSON library:

var attacks = JsonConvert.DeserializeObject<Dictionary<string, Attack>>(jsonString);

public class Attack {
    [JsonProperty("code")]
    public string Code { get; set; }

    [JsonProperty("timestamp_started")]
    public long TimestampStarted { get; set; }

    [JsonProperty("timestamp_ended")]
    public long TimestampEnded { get; set; }

    [JsonProperty("attacker_id")]
    public int AttackerId { get; set; }

    [JsonProperty("attacker_name")]
    public string AttackerName { get; set; }

    [JsonProperty("attacker_faction")]
    public int AttackerFaction { get; set; }

    [JsonProperty("attacker_factionname")]
    public string AttackerFactionName { get; set; }

    [JsonProperty("defender_id")]
    public int DefenderId { get; set; }

    [JsonProperty("defender_name")]
    public string DefenderName { get; set; }

    [JsonProperty("defender_faction")]
    public int DefenderFaction { get; set; }

    [JsonProperty("defender_factionname")]
    public string DefenderFactionName { get; set; }

    [JsonProperty("result")]
    public string Result { get; set; }

    [JsonProperty("stealthed")]
    public int Stealthed { get; set; }

    [JsonProperty("respect_gain")]
    public decimal RespectGain { get; set; }

    [JsonProperty("chain")]
    public int Chain { get; set; }

    [JsonProperty("modifiers")]
    public Dictionary<string, int> Modifiers { get; set; }
}

This results in a collection of identifiers against a strongly typed field set.

var allAttacksByFaction = attacks.Where(x => x.Value.AttackerFaction == 1234);
var singleAttack = attacks.Single(x => x.Key == "<value of attack identifier>");

2 Comments

You sure about the line ? var attacks = JsonConvert.Deserialize<Dictionary<string, Attack>>(jsonString); ? JsonConvert.Deserialize does not appear to be part of NewtonSofts library
I think it's 'DeserialiseObject<T>', VS2019 is updating thus I didn't write this within an IDE. You get the concept though.
1

Try this json converter.

class AttacksConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Attacks[]);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject attacks = serializer.Deserialize<JObject>(reader);
        Dictionary<string, AttackDetails> result = new Dictionary<string, AttackDetails>();

        foreach (JProperty property in attacks.Properties())
        {
            string attackKey = property.Name;
            Attacks attackValue = property.Value.ToObject<Attacks>();
            
            result.Add(attackKey, new AttackDetails()
            {
                Code = attackValue.Code
            });
        }

        return result;
    }


    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

The full code can be found here: https://github.com/tmutton/StackOverflowQuestion63544325

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.