简体   繁体   中英

How to deserialize a json string containing property type in C#?

I am receiving this weird Json string from an API, that also contains the schema within it. I am trying to convert it to a simple model and I am not being able to do it because of this schema structure.

The Json sample is:

[{
"key":null,
"value":{"session_id":{"string":"xxxxx"},
"title_id_type":{"string":"server"},
"event_name":{"string":"achievement"},
"event_type":{"string":"server"},
"event_step":{"int":8},
"country":{"string":"US"},
"event_params":{"map":{"cdur":"416","gdur":"416","sdur":"416","tdur":"0","type":"challenge","percent":"100","status":"expired"}},
"device_id_map":{"map":{}},
"experiment_id_list":{"array":[]}
]}

I would expect this model to be:

public class MyModel
{
   [JsonProperty("session_id")]
   public string SessionId {get;set;}
   [JsonProperty("title_id_type")]
   public string TitleIdType{get;set;}
   [JsonProperty("event_step")]
   public int EventStep {get;set;}
   ...
}

So far, my tests resulted in objects where those string and int are property names, messing up the whole thing.

How could I deserialize such Json in a clean way? I am using .NET Core 5 and Newtonsoft.Json but no restrictions for a new library.

Because it is unknown how deep the nested properties might go and it is also unknown what types need to be handled, the simplest option is to create a custom converter that you apply to each property that contains a schema object:

public class KeyValueConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public class KeyValueConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var token = JObject.Load(reader);

        if (!token.HasValues)
            throw new Exception("KeyValueConverter requires one property per target object");

        if (token.Values().Count() > 1)
            throw new Exception("KeyValueConverter does not support multiple properties per target object");
            
        var prop = token.First.ToObject<JProperty>();
        return prop.Value.ToObject(objectType);
    }

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

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

You can then apply it to each property that will contain a schema object and based on the type of the target property, it will write the value as the correct target type.

var o = JsonConvert.DeserializeObject<List<KeyValuePair<string, MyModel>>>(json);

public class MyModel
{
    [JsonProperty("session_id")]
    [JsonConverter(typeof(KeyValueConverter))]
    public string SessionId { get; set; }
    [JsonProperty("title_id_type")]
    [JsonConverter(typeof(KeyValueConverter))]
    public string TitleIdType { get; set; }
    [JsonProperty("event_step")]
    [JsonConverter(typeof(KeyValueConverter))]
    public int EventStep { get; set; }
    
    [JsonProperty("event_params")]
    [JsonConverter(typeof(KeyValueConverter))]
    public Dictionary<string, object> EventParams { get; set; }

    [JsonProperty("experiment_id_list")]
    [JsonConverter(typeof(KeyValueConverter))]
    public List<object> ExperimentIdList { get; set; }
}

Its because model does not have definition for the json objects inside object root. I suppose that you are usign System.Text or other, anyway when this libreries read {} inside an string with json format they interpret that it would be deserialize like other class. So your model would have to have propierties with type of the class that have definitions for keys inside {}.

I've copied your json and paste like classes and get something like this

    public class Rootobject
    {
        public object key { get; set; }
        public Value value { get; set; }
    }

    public class Value
    {
        public Session_Id session_id { get; set; }
        public Title_Id_Type title_id_type { get; set; }
        public Event_Name event_name { get; set; }
        public Event_Type event_type { get; set; }
        public Event_Step event_step { get; set; }
        public Country country { get; set; }
        public Event_Params event_params { get; set; }
        public Device_Id_Map device_id_map { get; set; }
        public Experiment_Id_List experiment_id_list { get; set; }
    }

    public class Session_Id
    {
        public string _string { get; set; }
    }

    public class Title_Id_Type
    {
        public string _string { get; set; }
    }

    public class Event_Name
    {
        public string _string { get; set; }
    }

    public class Event_Type
    {
        public string _string { get; set; }
    }

    public class Event_Step
    {
        public int _int { get; set; }
    }

    public class Country
    {
        public string _string { get; set; }
    }

    public class Event_Params
    {
        public Map map { get; set; }
    }

    public class Map
    {
        public string cdur { get; set; }
        public string gdur { get; set; }
        public string sdur { get; set; }
        public string tdur { get; set; }
        public string type { get; set; }
        public string percent { get; set; }
        public string status { get; set; }
    }

    public class Device_Id_Map
    {
        public Map1 map { get; set; }
    }

    public class Map1
    {
    }

    public class Experiment_Id_List
    {
        public object[] array { get; set; }
    }

So all this classes are needed to deserialize your json string to an object, thats why its better that your string take other format before deserialize by deleting brackets { } or whatever you see convenient.

You could try Cinchoo ETL , an open source library to manage this conversion with few lines of code

Here is one way to accomplish using this library without type converters

string json = @"
[
  {
    ""key"": null,
    ""value"": {
      ""session_id"": { ""string"": ""pFEe0KByL/Df:3170:5"" },
      ""title_id_type"": { ""string"": ""server"" },
      ""event_name"": { ""string"": ""achievement"" },
      ""event_type"": { ""string"": ""server"" },
      ""event_step"": { ""int"": 8 },
      ""country"": { ""string"": ""US"" },
      ""event_params"": {
        ""map"": {
          ""cdur"": ""416"",
          ""gdur"": ""416"",
          ""sdur"": ""416"",
          ""tdur"": ""0"",
          ""type"": ""challenge"",
          ""percent"": ""100"",
          ""status"": ""expired""
        }
      },
      ""device_id_map"": { ""map"": {} },
      ""experiment_id_list"": { ""array"": [""a"", ""b""] },
      ""experiment_id_list1"": { ""array"": [1, 2] },
    }
  }
]";

using (var r = ChoJSONReader<Root1>.LoadText(json)
    .Setup(s => s.BeforeRecordFieldLoad += (o, e) =>
    {
        JObject jo = e.Source as JObject;
        if (jo != null)
        {
            if (e.PropertyName != "value")
            {
                var prop = jo.First.ToObject<JProperty>();
                e.Source = prop.Value;
            }
        }
    })
    )
{
    foreach (var rec in r)
        Console.WriteLine(rec.Dump());
}

Disclaimer: I'm author of this library

this code was tested in Visual Studio

    
var json=jsonOrig.Replace(":{\"string\":",":").Replace(":{\"int\":",":").Replace("},",",").Replace("}}","}");
    
var jsonDeserialized = JsonConvert.DeserializeObject<Data>(json); 

Class

public class Data
{
    public string session_id { get; set; }
    public string title_id_type { get; set; }
    public string event_name { get; set; }
    public string event_type { get; set; }
    public int event_step { get; set; }
    public string level { get; set; }
    public string country { get; set; }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM