user2404597
user2404597

Reputation: 508

DESerialize JSON to c# objects dynamically

I am getting JSON data from a webservice. it is providing me with FORM DATA with different questions and answers. every answer is a different c# object. I am trying to find the best way to map the ANSWERS to correct c# object.

for example if Question Id is "37" Then its a Address Object.

I have JSON String like in this format below

"answers": {
    "37": {
              "name": "yourAddress37",
              "order": "6",
              "sublabels": "{\"cc_firstName\":\"First Name\",\"cc_lastName\":\"Last Name\",\"cc_number\":\"Credit Card Number\",\"cc_ccv\":\"Security Code\",\"cc_exp_month\":\"Expiration Month\",\"cc_exp_year\":\"Expiration Year\",\"addr_line1\":\"Street Address\",\"addr_line2\":\"Street Address Line 2\",\"city\":\"City\",\"state\":\"State \\/ Province\",\"postal\":\"Postal \\/ Zip Code\",\"country\":\"Country\"}",
              "text": "Your Home Address:",
              "type": "control_address",
              "answer": {
                "addr_line1": "148 east 38st ",
                "addr_line2": "",
                "city": "Brooklyn ",
                "state": "Ny",
                "postal": "11203",
                "country": ""
              },
              "prettyFormat": "Street Address: 148 east 38st <br>City: Brooklyn <br>State / Province: Ny<br>Postal / Zip Code: 11203<br>"
            },
     "38": {
              "name": "emergencyContact",
              "order": "9",
              "sublabels": "{\"prefix\":\"Prefix\",\"first\":\"First Name\",\"middle\":\"Middle Name\",\"last\":\"Last Name\",\"suffix\":\"Suffix\"}",
              "text": "Emergency Contact Name:",
              "type": "control_fullname",
              "answer": {
                "first": "Pauline ",
                "last": "Sandy "
              },
              "prettyFormat": "Pauline  Sandy "
            }
}

and it MAPS to following c# property

public Dictionary<int, answer> answers{ get; set; }

Then I have a generic Answer class

public class answer
{
    public string name { get; set; }
    public dynamic answer { get; set; }
}

if you look at the ANSWER data from json then you will see its different for every question. for example one answer would be ADDRESS OBJECT, other answer would be FIRST & LAST NAME object.

my question is, how can i deserialize json into correct objects/properties automatically? I can create different POCO objects, such as address & ProfileName, but how would i map them automatically to correct object/property.

EDIT:

Loop through all Answers

        foreach (var a in item.answers)
        {
            // pass the ANSWER OBJECT (dynamic data type) to function
            createNewApplication(System.Convert.ToInt16(a.Key), a.Value.answer,ref app);               

        }


private void createNewApplication(int key, dynamic value,ref HcsApplicant app)
{

    if (key == 4) // data is plain string
        app.yourPhone = value;
    if (key == 8)
        app.yourEmail = value;
    if (key==37) // data is a object
        app.address = value.ToObject<address>();


}

is this approach OK? any cleaner way of doing it?

Upvotes: 1

Views: 158

Answers (3)

Hullburg
Hullburg

Reputation: 111

Make a constructor for each answer type that constructs by parsing a JSON object string. Make all the answers implement an interface, e.g. IAnswer. Map all constructors (as functions) to the corresponding question IDs in a dictionary. Lastly, loop through the questions, call each constructor, and maybe put them in a new dictionary. Example code:

    interface IAnswer { };

    public class ExampleAnswer : IAnswer
    {
        public ExampleAnswer(String JSONObject)
        {
            // Parse JSON here
        }
    }

    delegate IAnswer AnswerConstructor(String JSONObject);

    Dictionary<int, AnswerConstructor> Constructors = new Dictionary<int, AnswerConstructor>()
    {
        {1234, ((AnswerConstructor)(json => new ExampleAnswer(json)))}
        // Add all answer types here
    };

    Dictionary<int, IAnswer> ParseAnswers(Dictionary<int, String> JSONObjects)
    {
        var result = new Dictionary<int, IAnswer>();
        foreach (var pair in JSONObjects)
            result.Add(pair.Key, Constructors[pair.Key](pair.Value));

        return result;
    }

Edit: Look at Matt's answer for some good options for how to parse JSON.

Edit2, In response to your edit: That looks like a good way of doing it! I think it's better than my answer, since you can keep all type information, unlike my method.

The only thing I see that you might want to change is using else if or switch instead of multiple ifs. This could increase performance if you have many answers.

Upvotes: 2

Alberto Chiesa
Alberto Chiesa

Reputation: 7350

I personally don't like every option that involves custom parsing and looking directly on the questions.

You can make use of partial deserialization via JToken class.

Just declare your answers dictionary as such:

public Dictionary<int, JToken> Answers{ get; set; }

And then whenever you need the address page you can simply do Answers[37].ToObject<Address>(). How you manage to call this method, depends upon the rest of your code, but you can embed it in properties, in a big switch, in multiple methods, one for each class. One option I like is to have a static From method in each deserializable class:

public class Address
{
    public string Name { get; set; }

    // all the othe properties
    // ....

    public static Address From(Dictionary<int, JToken> answers)
    {
        return answers?.TryGetValue(37, out var address) ?? false
            ? address?.ToObject<Address>()
            : null;
    }
}

// so you can just write:
var address = Address.From(answers);

As a side note, remember that the default deserialization settings for Json.Net are case insensitive, so you can deserialize the name property from JSON to a more idiomatic Name property on your POCOs.

Upvotes: 2

Matt Stannett
Matt Stannett

Reputation: 2728

You have a couple of options:

  1. Deserialize into a dynamic object using the System.Web package as per this answer or the JSON.Net package as per this answer then use conditional checks/the null propagation operator to access a property.

  2. Automatically deserialize down to the level where there are differences, then have code to manual deserialize the properties that are different into the correct POCO types on your parent Deserialized object.

  3. Leverage one of the Serialization Callbacks provided by JSON.Net (OnDeserializing or OnDeserialized) to handle populating the different properties into the correct types as part of the deserialization pipeline.

With approaches 2 and 3 you could write a nicer helper method on your POCO that inspected the objects properties and returned a result which would be the type that was set (I would recommend returning an Enum) e.g.:

public PropertyTypeEnum GetPropertyType(MyPocoClass myPocoClass)
{
    if (myPocoClass.PropertyOne != null)
    {
        return PropertyTypeEnum.TypeOne;
    }
    else if (...)
    {
        return PropertyTypeEnum.TypeN
    }
    else
    {
        // probably throw a NotImplementedException here depending on your requirements
    }
}

Then in your code to use the object you can use the returned Enum to switch on the logical paths of your code.

Upvotes: 1

Related Questions