0

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


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"
        }
        ....
    }
....
D-Shih
  • 44,943
  • 6
  • 31
  • 51
ollie10
  • 317
  • 2
  • 11

4 Answers4

1

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 ...
}
Mark
  • 56
  • 1
  • 9
  • Even if it is not the cleanest solution, It seems to work but as everything in life generated other problems. I have a Gallery proprerty (see my answer) with the same idiotic structure, this Isn't serialized as well and I cannot split the JSON only for this property. The rest is serialized except this one property – ollie10 Mar 25 '22 at 16:58
1

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

You can handle that using the concept of overflow JSON: https://learn.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.

Alberto
  • 15,626
  • 9
  • 43
  • 56
  • Grazie! I think this is the best solution so far, I was able to deserialize partially the object then deserialize the gallery and recompose it, it is a completely weird schema but it is working – ollie10 Mar 26 '22 at 11:21
1

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; }
}
Serge
  • 40,935
  • 4
  • 18
  • 45
1

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
    
spzvtbg
  • 964
  • 5
  • 13
  • Many thanks, I tried, it seems it works also without the wrapper only with the JsonExtensionData deserializing it partially and then recomposing – ollie10 Mar 26 '22 at 11:22
  • @ollie10 - great, the wrapper can be reused for the nested types with the same structure that you mentioned earlier. – spzvtbg Mar 26 '22 at 12:20