0

I am trying to deserialize a JSON object and would like to use its property name as an attribute, but I don't know how to do so.

Here are my model classes:

public partial class Questions
{
    [JsonProperty("timestamp")]
    public DateTimeOffset TimeStamp { get; set; }

    [JsonProperty("choices")]
    public Choices choices { get; set; }
}

public partial class Choices
{
    [JsonProperty("choices")]
    public string[] choices { get; set; }
}

Here is the associated JSON:

{
  "id": "5e6106600066d227a231ceb8",
  "complete": null,
  "questions": {
    "5e60af61a7be775b0d31ea77": {
      "timeStamp": "2020-03-05T15:01:56.000000Z",
      "choices": [
        "dsbb"
      ]
    },
    "5e60af66a7be775b0d31ea78": {
      "timeStamp": "2020-03-05T15:02:02.000000Z",
      "choices": [
        "9999999999"
      ]
    },
    "5e60af76dd15333d1727ce09": {
      "timeStamp": "2020-03-05T15:02:11.000000Z",
      "choices": [
        "lj@test.com"
      ]
    },
    "5e60afeeb406ed608058d045": {
      "timeStamp": "2020-03-05T15:02:15.000000Z",
      "choices": [
        0
      ]
    },
    "5e5d282331808f44ce4b0b76": {
      "timeStamp": "2020-03-05T15:02:22.000000Z",
      "choices": [
        0
      ]
    },
    "5e5cec17ae23a40b0c645614": {
      "timeStamp": "2020-03-05T15:02:29.000000Z",
      "choices": [
        0
      ]
    },
    "5e5d08d235bf95782b049cb3": {
      "timeStamp": "2020-03-05T15:02:34.000000Z",
      "choices": [
        2
      ]
    },
    "5e5d0a05a0be6b6533195f17": {
      "timeStamp": "2020-03-05T15:02:43.000000Z",
      "choices": [
        0
      ]
    },
    "5e5cecdcf3c27f611b3df2fa": {
      "timeStamp": "2020-03-05T15:03:01.000000Z",
      "choices": [
        "100"
      ]
    },
    "5e5cedd7949da059190f2146": {
      "timeStamp": "2020-03-05T15:03:10.000000Z",
      "choices": [
        1,
        3,
        4
      ]
    },
    "5e60e8e899017615e27ad107": {
      "timeStamp": "2020-03-05T15:03:15.000000Z",
      "choices": [
        0
      ]
    },
    "5e60e95d479b812cb4777b2f": {
      "timeStamp": "2020-03-05T15:03:22.000000Z",
      "choices": [
        0
      ]
    },
    "5e60e9feff05631d3b0585d8": {
      "timeStamp": "2020-03-05T15:03:59.000000Z",
      "choices": [
        "fveg"
      ]
    }
  },
  "ip_address": "188.165.111.130",
  "created_at": "2020-03-05T14:02:08.621000Z",
  "updated_at": "2020-03-05T14:04:21.995000Z"
}

And finally, my binding code:

JObject respondants= JObject.Parse(json);
IList<JToken> questions = new List<JToken>();
questions = respondants["questions"].Children().ToList();
foreach(JToken q in questions){
    Questions question = q.ToObject<Questions>();
}

Trying to do so, I am facing the following exception:

Newtonsoft.Json.JsonSerializationException' s'est produite dans System.Private.CoreLib.dll: 'Unexpected token while deserializing object: PropertyName

Any idea?

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
Pierre
  • 31
  • 5
  • When a property name is like `"5e60af61a7be775b0d31ea77"` Thats a pretty good sign that you are dealing with a dictionary. Upon serialisation the key are turn to property to ensure uniqness. You can create the model simply. Format your json, remove all the dictionary element except a few and rename the key as 1,2,3 etc. Then tools like https://app.quicktype.io/?share=xBBgePwCLHEFeCVbD0Id will automatically see the dictionary and create the class. – Drag and Drop Aug 07 '20 at 15:00
  • Notice that it also saw the different type in choices and started to make aggregation to rules them all. Do you control the creation of the Json ? Coul you add a type information into question or choice? – Drag and Drop Aug 07 '20 at 15:02

3 Answers3

0

You may prepare a fiddle sample before asking.

        string json = "{\"id\":\"5e6106600066d227a231ceb8\",\"complete\":null,\"questions\":{\"5e60af61a7be775b0d31ea77\":{\"timeStamp\":\"2020-03-05T15:01:56.000000Z\",\"choices\":[\"dsbb\"]},\"5e60af66a7be775b0d31ea78\":{\"timeStamp\":\"2020-03-05T15:02:02.000000Z\",\"choices\":[\"9999999999\"]},\"5e60af76dd15333d1727ce09\":{\"timeStamp\":\"2020-03-05T15:02:11.000000Z\",\"choices\":[\"lj@test.com\"]},\"5e60afeeb406ed608058d045\":{\"timeStamp\":\"2020-03-05T15:02:15.000000Z\",\"choices\":[0]},\"5e5d282331808f44ce4b0b76\":{\"timeStamp\":\"2020-03-05T15:02:22.000000Z\",\"choices\":[0]},\"5e5cec17ae23a40b0c645614\":{\"timeStamp\":\"2020-03-05T15:02:29.000000Z\",\"choices\":[0]},\"5e5d08d235bf95782b049cb3\":{\"timeStamp\":\"2020-03-05T15:02:34.000000Z\",\"choices\":[2]},\"5e5d0a05a0be6b6533195f17\":{\"timeStamp\":\"2020-03-05T15:02:43.000000Z\",\"choices\":[0]},\"5e5cecdcf3c27f611b3df2fa\":{\"timeStamp\":\"2020-03-05T15:03:01.000000Z\",\"choices\":[\"100\"]},\"5e5cedd7949da059190f2146\":{\"timeStamp\":\"2020-03-05T15:03:10.000000Z\",\"choices\":[1,3,4]},\"5e60e8e899017615e27ad107\":{\"timeStamp\":\"2020-03-05T15:03:15.000000Z\",\"choices\":[0]},\"5e60e95d479b812cb4777b2f\":{\"timeStamp\":\"2020-03-05T15:03:22.000000Z\",\"choices\":[0]},\"5e60e9feff05631d3b0585d8\":{\"timeStamp\":\"2020-03-05T15:03:59.000000Z\",\"choices\":[\"fveg\"]}},\"ip_address\":\"188.165.111.130\",\"created_at\":\"2020-03-05T14:02:08.621000Z\",\"updated_at\":\"2020-03-05T14:04:21.995000Z\"}";
        JObject respondants = JObject.Parse(json);
        foreach (JProperty x in respondants["questions"])
        { 
            Console.WriteLine(x.Name);
            Console.WriteLine(x.Value);
        }

Like this: https://dotnetfiddle.net/f5jEgS

Muzaffer Galata
  • 580
  • 1
  • 9
  • 22
0

After answering once I realized I misread your question. Hopefully this one will hit the mark.

Your questions json contains objects with the names like "5e60af61a7be775b0d31ea77", and these have no matching classes in your definitions, so they are unexpected.

You need to use use the JToken class (or JObject class) and from that cast the question object to a Property, which explicitly has name and value attributes you can access to get the object name "5e60af61a7be775b0d31ea77".

Then you can use the first child to obtain the details of Timestamp and choices

JToken respondants = JToken.Parse(json);
IList<JToken> questions = new List<JToken>();
List<Question> replies = new List<Question>();

questions = respondants["questions"].Children().ToList();
foreach (JToken q in questions)
{
   Question question = new Question
   {
       QuestionId = (q as JProperty).Name,
       QuestionDetail = q.Children().First().ToObject<Detail>()
   };
   replies.Add(question);
}



public class Question
{
    public string QuestionId { get; set; }
    [JsonProperty("timestamp")]
    public Detail QuestionDetail { get; set; }
}

public class Detail
{
    [JsonProperty("timestamp")]
    public DateTimeOffset TimeStamp { get; set; }

    [JsonProperty("choices")]
    public List<string> Choices { get; set; }
}

I have simplified your class definition a little as well, and probably you want a list of strings rather than an array of strings. Array versus List: When to use which?

Also notice that your json does not have very consistent model, though it is valid json. In the 4th question the 'choices' array switches from an array of strings to numbers.

robs
  • 840
  • 10
  • 15
0

It looks like you want to use model classes to deserialize your data, but you are having trouble handling the dynamic hexadecimal property names inside the questions object. To get around it you are attempting to use JObjects/JTokens. While this approach will work if you do it correctly, there is an easier way. You can use a Dictionary<string, Question> in your model to handle the dynamic property names. Make your classes like this:

public class RootObject
{
    [JsonProperty("id")]
    public string Id { get; set; }

    [JsonProperty("questions")]
    public Dictionary<string, Question> Questions { get; set; }

    [JsonProperty("ip_address")]
    public string IpAddress { get; set; }

    [JsonProperty("created_at")]
    public DateTimeOffset CreatedAt { get; set; }

    [JsonProperty("updated_at")]
    public DateTimeOffset UpdatedAt { get; set; }
}

public partial class Questions
{
    [JsonProperty("timestamp")]
    public DateTimeOffset TimeStamp { get; set; }

    [JsonProperty("choices")]
    public List<string> Choices { get; set; }
}

Then you can deserialize like this:

var root = JsonConvert.DeserializeObject<RootObject>(json);

Here is a working demo: https://dotnetfiddle.net/VE7upc

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300