Home > Net >  C# deserialize Json with Indexer nodes along with other values
C# deserialize Json with Indexer nodes along with other values

Time:03-26

Good Morning, I'm falling on a problem that apparently should be easy but I cannot find a solution.

I have already red this and this posts, nothing helped.

I have an API, that returns data in JSON format with this schema, it has an indexer (I know it's idiotic, I didn't develop it) of the result before each object:

{
   "total":2,
   "0":{
      "id_property":5028080,
      "id_company":11719097,
      ....
   },
   "1":{
      "id_property":4996958,
      "id_company":11719097,
      ....
   },
   "status":"success"
}

I'm trying to deserialize it with System.Text.Json but it fails also with Newtonsoft.

According to the previous links I made a C# schema like this:

public class RootDTO
{
    public int total { get; set; }
    public Dictionary<string, PropertyDTO> Property { get; set; }
    public string status { get; set; }
}

public class PropertyDTO
{
    public int id { get; set; }
    public string url { get; set; }
    // OTHER PROPERTIES ...
}

I tried to deserialize it with the root Object and directly with the Dictionary in this way, either way it is failing. Null property in the first case, exception in the second case since it finds a "total" property not matching the schema.

RootDTO result = JsonSerializer.Deserialize<RootDTO>(content);
Dictionary<string, PropertyDTO> result = JsonSerializer.Deserialize<Dictionary<string, PropertyDTO>>(content);

Any idea on how to solve this apparently simple problem? Many thanks everybody

CodePudding user response:

I think the problem is with your JSON structure.

Because elements 0, 1 are single objects which might not be Dictionary type.

If I understand correctly you might want to use an object containing elements 0, 1...

{
    "total": 2,
    "property": {
        "0": {
            "id_property": 5028080,
            "id_company": 11719097
        },
        "1": {
            "id_property": 4996958,
            "id_company": 11719097
        }
    },
    "status": "success"
}

You can try to use your class to do that.

if you can't modify your JSON structure, you can try to use JObject.Parse method from Newtonsoft.Json library.

JObject root =JObject.Parse(content);
var obj0 = root["0"].ToObject<Dictionary<string, string>>();
var obj1 = root["1"].ToObject<Dictionary<string, string>>();

CodePudding user response:

You can get the values using a method like this where we deserialize to a dict of string keys and take any object so the code won't complain about the various types we have. Then further break it down into the specific fields you need.

EDIT - Try the code here

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;

public class Program
{
    public static void Main()
    {
        var json = @"{
                   ""total"": 2,
                   ""0"": {
                      ""id_property"": 5028080,
                      ""id_company"": 11719097
                    },
                    ""1"": {
                      ""id_property"": 4996958,
                      ""id_company"": 11719097
                    },
                    ""status"": ""success""
                 }";        
        
        var data = JsonSerializer.Deserialize<Dictionary<string,object>>(json);
        
        foreach (var obj in data.Where(t => t.Key != "total" && t.Key != "status")) {
            var dataObj = JsonSerializer.Deserialize<PropertyDTO>(obj.Value.ToString());
            Console.WriteLine(dataObj.id_property);
            Console.WriteLine(dataObj.id_company);
        }
    }   
}

public class PropertyDTO
{
    public int id_property { get; set; }
    public int id_company { get; set; }
    // OTHER PROPERTIES ...
}

CodePudding user response:

Another thing I wasn't aware is that inside I have a SubObject with the same idiotic format

....
"galleries": [
    {
        "id": 4441310,
        "0": {
            "id": 146843541,
            "url": "xxx",
            "description": "",
            "filename": "83732120220325094904.jpg",
            "position": 1,
            "url_big": "yyy",
            "url_original": "kkk"
        },
        "1": {
            "id": 146843542,
            "url": "xxx",
            "description": "",
            "filename": "83732220220325094904.jpg",
            "position": 2,
            "url_big": "yyy",
            "url_original": "kkk"
        }
        ....
    }
....

CodePudding user response:

The issue there is that the objects are not represented by properties of the target type RootDTO.

You can handle the using the concept of overflow JSON: https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-handle-overflow?pivots=dotnet-6-0

public class RootDTO
{
    public int total { get; set; }
    public string status { get; set; }

    //all the "overflowing" properties will be here
    [System.Text.Json.Serialization.JsonExtensionData]
    public Dictionary<string, JsonElement> Properties { get; set; }
}


RootDTO result = System.Text.Json.JsonSerializer.Deserialize<RootDTO>(json);

You can later deserialize the JsonElement in your PropertyDTO type.

CodePudding user response:

the easiest way would be to use Newtonsoft.Json. You can try to use JsonExtensionData , but in this case you will have only a generic < string, object > dictionary. This code creates a typed dictionary

using Newtonsoft.Json;

var jsonParsed = JObject.Parse(json);
    
RootDTO rootDto = new RootDTO
{
    total = (int)jsonParsed["total"],
    properties = jsonParsed.Properties().Where(p => p.Value.Type is JTokenType.Object)
   .ToDictionary(p =>p.Name, p=> p.Value.ToObject<PropertyDTO>());
    status = (string)jsonParsed["status"]
}

classes

public class RootDTO
{
    public int total { get; set; }
    public Dictionary<string, PropertyDTO> properties { get; set; }
    public string status { get; set; }
}

public class PropertyDTO
{
    [JsonProperty("id_property")]
    public int properyId { get; set; }
    [JsonProperty("id_company")]
    public int companyId { get; set; }
}

CodePudding user response:

A work around can be the following:

  1. Create another class that wraps desired values

     public class IdioticWrapper<TValue> : IDictionary<string, JsonElement>
     {
         private IDictionary<string, TValue> desiredValues
             = new Dictionary<string, TValue>();
         private IDictionary<string, JsonElement> properties
             = new Dictionary<string, JsonElement>();
    
         public JsonElement this[string key] 
         { 
             get => this.properties[key]; 
             set
             {
                 // TODO: some checks like is null can deserialize ...
                 this.desiredValues[key] = JsonSerializer.Deserialize<TValue>(value.GetRawText());
             }
         }
    
         // There are desired models
         public IEnumerable<TValue> DesiredValues
             => this.desiredValues.Values;
    
         public ICollection<string> Keys => this.properties.Keys;
    
         public ICollection<JsonElement> Values => this.properties.Values;
    
         public int Count => this.properties.Count;
    
         public bool IsReadOnly => this.properties.IsReadOnly;
    
         public void Add(string key, JsonElement value) => this.properties.Add(key, value);
    
         public void Add(KeyValuePair<string, JsonElement> item) => this.properties.Add(item);
    
         public void Clear() => this.properties.Clear();
    
         public bool Contains(KeyValuePair<string, JsonElement> item) => properties.Contains(item);
    
         public bool ContainsKey(string key) => this.properties.ContainsKey(key);
    
         public void CopyTo(KeyValuePair<string, JsonElement>[] array, int arrayIndex) => this.properties.CopyTo(array, arrayIndex);
    
         public IEnumerator<KeyValuePair<string, JsonElement>> GetEnumerator() => this.properties.GetEnumerator();
    
         public bool Remove(string key) => this.properties.Remove(key);
    
         public bool Remove(KeyValuePair<string, JsonElement> item) => this.properties.Remove(item);
    
         public bool TryGetValue(string key, [MaybeNullWhen(false)] out JsonElement value) => this.properties.TryGetValue(key, out value);
    
         IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
     }
    

2 use that wraper wehenever you need to wrap nested data of that kind

    public class PropertyDTO
    {
        public int id_property { get; set; }

        public int id_company { get; set; }

        [JsonExtensionData]
        public IdioticWrapper<object> properties { get; set; }
    }

    public class RootDTO
    {
        public int total { get; set; }

        public string status { get; set; }

        [JsonExtensionData]
        public IdioticWrapper<PropertyDTO> properties { get; set; }

        public IEnumerable<PropertyDTO> Values 
            => this.properties.DesiredValues;
    }
  1. and at the end you shold get the correct result

     var result = JsonSerializer.Deserialize<RootDTO>(jsonContent);
    
     Console.WriteLine(result.total);
     Console.WriteLine(result.status);
    
     var values = result.Values;
     // ... and so on
    
  • Related