Home > OS >  How to deserialize Json into an C# object and fill the components into a dictionary
How to deserialize Json into an C# object and fill the components into a dictionary

Time:04-21

{
    "success": true,
    "data": [{
        "type": "Employee",
        "attributes": {
            "id": {
                "label": "ID",
                "value": 8556527,
                "type": "integer",
                "universal_id": "id"
            },
            "email": {
                "label": "Email",
                "value": "[email protected]",
                "type": "standard",
                "universal_id": "email"
            },
            "dynamic_2682839": {
                "label": "DATEV Personalnumber",
                "value": "31604",
                "type": "standard",
                "universal_id": "staff_number"
            }
        }
    }, 

public class Id
{
    [JsonPropertyName("label")]
    public string Label { get; set; }

    [JsonPropertyName("value")]
    public int Value { get; set; }

    [JsonPropertyName("type")]
    public string Type { get; set; }

    [JsonPropertyName("universal_id")]
    public string UniversalId { get; set; }
}

public class Email
{
    [JsonPropertyName("label")]
    public string Label { get; set; }

    [JsonPropertyName("value")]
    public string Value { get; set; }

    [JsonPropertyName("type")]
    public string Type { get; set; }

    [JsonPropertyName("universal_id")]
    public string UniversalId { get; set; }
}

public class Dynamic2682839
{
    [JsonPropertyName("label")]
    public string Label { get; set; }

    [JsonPropertyName("value")]
    public string Value { get; set; }

    [JsonPropertyName("type")]
    public string Type { get; set; }

    [JsonPropertyName("universal_id")]
    public string UniversalId { get; set; }
}

public class Attributes
{
    [JsonPropertyName("id")]
    public Id Id { get; set; }

    [JsonPropertyName("email")]
    public Email Email { get; set; }

    [JsonPropertyName("dynamic_2682839")]
    public Dynamic2682839 Dynamic2682839 { get; set; }
}

public class Datum
{
    [JsonPropertyName("type")]
    public string Type { get; set; }

    [JsonPropertyName("attributes")]
    public Attributes Attributes { get; set; }
}

public class Root
{
    [JsonPropertyName("success")]
    public bool Success { get; set; }

    [JsonPropertyName("data")]
    public List<Datum> Data { get; set; }
}

I know it is probably are very simple solution behind it but i cant get to it. My Problem is that I want to deserialize this type of data into an object for example. This json file goes on forever but the only thing I'm interested in are the "ID" and the "Personalnumber" so I can create a dictionary for further processing with my code.

I mean I could just sit there for two days and add everything to the dictionary but first of all it would drive me crazy and second of all the dictionary should add new members automatically to the dictionary and shouldn't be static. I already converted the Json into class objects and downloaded Newtonsoft for my IDE and with this comand:

Id ids = JsonConvert.DeserializeObject<Id>(json);
Console.WriteLine(ids.Value);

I try to get all the values from the ID´s but all i get is a 0. The thing is i tested everything so far and it works perfectly but my deserialization dont work as planned.

If anyone could help a newbie I would appreciate it.

CodePudding user response:

This looks wrong

Id ids = JsonConvert.DeserializeObject<Id>(json);

You should probably be deserializing to Root instead.

Root root = JsonConvert.DeserializeObject<Root>(json);

From there you can get all the IDs with

List<Id> ids = root.Data.Select(datum => datum.Attributes.Id);

CodePudding user response:

I don't know if you used QuickType.IO for generating your JSON classes but, if you didn't, I would recommend it; it's a bit more sophisticated than other generators. You can trick it into helping you more by modifying your JSON..

Take this:

{
  "success": true,
  "data": [
    {
      "type": "Employee",
      "attributes": {
        "id": {
          "label": "ID",
          "value": 8556527,
          "type": "integer",
          "universal_id": "id"
        },
        "email": {
          "label": "Email",
          "value": "[email protected]",
          "type": "standard",
          "universal_id": "email"
        },
        "dynamic_2682839": {
          "label": "DATEV Personalnumber",
          "value": "31604",
          "type": "standard",
          "universal_id": "staff_number"
        }
      }
    }
  ]
}

And make it into this (put some more objects in "attributes", and make the keys of the dictionary into sequential numbers):

{
    "success": true,
    "data": [{
        "type": "Employee",
        "attributes": {
            "1": {
                "label": "ID",
                "value": 8556527,
                "type": "integer",
                "universal_id": "id"
            },
            "2": {
                "label": "Email",
                "value": "[email protected]",
                "type": "standard",
                "universal_id": "email"
            },
            "3": {
                "label": "DATEV Personalnumber",
                "value": "31604",
                "type": "standard",
                "universal_id": "staff_number"
            },
            "4": {
                "label": "DATEV Personalnumber",
                "value": "31604",
                "type": "standard",
                "universal_id": "staff_number"
            },
            "5": {
                "label": "DATEV Personalnumber",
                "value": "31604",
                "type": "standard",
                "universal_id": "staff_number"
            }
        }
    }]
}

which will help QT see more easily that a Dictionary<string, Attribute> is suitable for "attributes", and a custom converter is needed for the varying type of "value"...

enter image description here

Otherwise you'll just get multiple identical classes (your Id, Email and Dynamic_123 classes) and a property for every member of "attributes" even though they're all the same:

enter image description here

You thus get these classes out of QT:

namespace SomeNamespace
{
    using System;
    using System.Collections.Generic;

    using System.Globalization;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Converters;

    public partial class SomeRoot
    {
        [JsonProperty("success")]
        public bool Success { get; set; }

        [JsonProperty("data")]
        public Datum[] Data { get; set; }
    }

    public partial class Datum
    {
        [JsonProperty("type")]
        public string Type { get; set; }

        [JsonProperty("attributes")]
        public Dictionary<string, Attribute> Attributes { get; set; }
    }

    public partial class Attribute
    {
        [JsonProperty("label")]
        public string Label { get; set; }

        [JsonProperty("value")]
        public Value Value { get; set; }

        [JsonProperty("type")]
        public string Type { get; set; }

        [JsonProperty("universal_id")]
        public string UniversalId { get; set; }
    }

    public partial struct Value
    {
        public long? Integer;
        public string String;

        public static implicit operator Value(long Integer) => new Value { Integer = Integer };
        public static implicit operator Value(string String) => new Value { String = String };
    }

    public partial class SomeRoot
    {
        public static SomeRoot FromJson(string json) => JsonConvert.DeserializeObject<SomeRoot>(json, SomeNamespace.Converter.Settings);
    }

    public static class Serialize
    {
        public static string ToJson(this SomeRoot self) => JsonConvert.SerializeObject(self, SomeNamespace.Converter.Settings);
    }

    internal static class Converter
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
            Converters =
            {
                ValueConverter.Singleton,
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
            },
        };
    }

    internal class ValueConverter : JsonConverter
    {
        public override bool CanConvert(Type t) => t == typeof(Value) || t == typeof(Value?);

        public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
        {
            switch (reader.TokenType)
            {
                case JsonToken.Integer:
                    var integerValue = serializer.Deserialize<long>(reader);
                    return new Value { Integer = integerValue };
                case JsonToken.String:
                case JsonToken.Date:
                    var stringValue = serializer.Deserialize<string>(reader);
                    return new Value { String = stringValue };
            }
            throw new Exception("Cannot unmarshal type Value");
        }

        public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
        {
            var value = (Value)untypedValue;
            if (value.Integer != null)
            {
                serializer.Serialize(writer, value.Integer.Value);
                return;
            }
            if (value.String != null)
            {
                serializer.Serialize(writer, value.String);
                return;
            }
            throw new Exception("Cannot marshal type Value");
        }

        public static readonly ValueConverter Singleton = new ValueConverter();
    }
}

and you can get your ID/Personalnumber attributes like this:

        var someRoot = SomeRoot.FromJson(File.ReadAllText("a.json"));

        var attribs = someRoot.Data.SelectMany(d => d.Attributes.Where(a => a.Value.Label == "ID" || a.Value.Label.Contains("Personalnumber"))).ToArray();

or some other LINQ of your choosing on the Dictionary at the path "data"."attributes" - here I've chosen to SelectMany; if Data had 10 elements, and each element had 4 Attributes, 2 of which were ID or Personalnumber, you'd get a single array of 20 long, of the ID/Personalnumber Attributes. You might not want to SelectMany, if you want to keep them together (they're only loosely related by ordering in the array after a SelectMany)

  • Related