0

Consider the following: You have a service which returns a JSON string, and in your C# code you would like to use the returned value as an object (e.g.: get the various properties of it by their names).

Sample JSON:

{ 
    "Name": "Jon Smith", 
    "Address": 
    { 
        "City": "New York",
        "State": "NY" 
    },
    "Age": 42 
}

Sample C# usage:

var object = ... // this is what I am asking for
Console.WriteLine(object.Name); // this should print out "Jon Smith"
Console.WriteLine(object.Address.State); // this should print out "NY"

What options are available in C# without using third party libraries?

qqbenq
  • 10,220
  • 4
  • 40
  • 45

1 Answers1

5

If you don't know the exact data contract (or you don't care about it), then you can easily do that with the help of System.Web.Helpers.Json class and a dynamic object:

dynamic json = System.Web.Helpers.Json.Decode(@"{ ""Name"": ""Jon Smith"", ""Address"": { ""City"": ""New York"", ""State"": ""NY"" }, ""Age"": 42 }");
Console.WriteLine(json.Name);           // prints "Jon Smith"
Console.WriteLine(json.Address.State);  // prints "NY"

A note on that: you will have to add reference to the System.Web.Helpers assembly.

Of course not everyone likes dynamic, there are some people who prefer to have well defined data contracts. For them the solution is a little bit longer:

You have to create matching classes for your data contracts, and attribute them accordingly:

// class for the root object:
[DataContract]
public class Person
{
    [DataMember]
    public string Name { get; set; }

    [DataMember]
    public Address Address { get; set; }

    [DataMember]
    public int Age { get; set; }
}

// class for the address object:
[DataContract]
public class Address
{
    [DataMember]
    public string City { get; set; }

    [DataMember]
    public string State { get; set; }
}

You can mark members with IgnoreDataMember attribute to, well, ignore them, or add IsRequired=true to make them mandatory.

After defining those contracts, you can easily parse the JSON string into a Person object:

DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Person));
using( MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(@"{ ""Name"": ""Jon Smith"", ""Address"": { ""City"": ""New York"", ""State"": ""NY"" }, ""Age"": 42 }")))
{
    var person = (Person)ser.ReadObject(stream);
    Console.WriteLine(person.Name);             // prints "Jon Smith"
    Console.WriteLine(person.Address.State);    // prints "NY"
}

Note: the DataContractJsonSerializer resides in the System.ServiceModel.Web assembly, so you will have to add a reference for that. (And of course for the System.Runtime.Serialization assembly as well.)

To make it easier to use, you can add static Parse and TryParse methods to your data contract class:

public static Person Parse(string jsonString)
{
    if (String.IsNullOrWhiteSpace(jsonString)) throw new ArgumentNullException("The jsonString parameter shouldn't be null or an empty string.");

    DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Person));
    using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString)))
    {
        return (Person)ser.ReadObject(stream);
    }
}

public static bool TryParse(string jsonString, out Person result)
{
    try
    {
        result = Person.Parse(jsonString);
        return true;
    }
    catch (Exception ex)
    {
        if (ex is ArgumentNullException || ex is SerializationException)
        {
            result = null;
            return false;
        }
        throw;
    }
}

As L.B. mentioned in their comment, you can use JavaScriptSerializer - if you add a reference to the System.Web.Extensions assembly - to make it even simpler:

var person = new JavaScriptSerializer().Deserialize<Person>(@"{ ""Name"": ""Jon Smith"", ""Address"": { ""City"": ""New York"", ""State"": ""NY"" }, ""Age"": 42 }");

For that you still need the classes from above, but you can leave out the - "ugly" - attributes if you would like to. (But with this method you lose the ability to mark parts as mandatory...)

qqbenq
  • 10,220
  • 4
  • 40
  • 45
  • Were you supposed to answer this under a different account? All looks a bit odd at the moment what with question and (very long) answer being within 1 minute of each other by the same user. – Fishcake Aug 26 '14 at 14:33
  • 1
    There is an option here to answer your own question... They get posted at the same time. I find it a quite good way to document problems you face, and have a way to find them later. – qqbenq Aug 26 '14 at 14:35
  • @qqbenq If you used `JavaScriptSerializer`, you wouldn't need those ugly attributes. A one line deserialization is also possible. `new JavaScriptSerializer().Deserialize(jsonstring)` – L.B Aug 26 '14 at 14:37
  • @L.B Thanks, I incorporated that in my answer, but that method also has some drawbacks: you lose the ability to mark fields as mandatory, if I'm not mistaken. – qqbenq Aug 26 '14 at 14:47
  • @qqbenq What do you mean by mandatory, if that field doesn't exist in json, you'll get the default value. BTW: You can use `ScriptIgnore` attribute if needed. – L.B Aug 26 '14 at 14:49
  • @qqbenq BTW: I am fan of Json.Net, which has the most control over serialization/deserialization process. No need for other ones.... See for example [this question](http://stackoverflow.com/questions/22052430/how-to-handle-json-that-returns-both-a-string-and-a-string-array/22053420#22053420) – L.B Aug 26 '14 at 14:51
  • @L.B By mandatory I mean that a field must be present in the json string, otherwise the parsing should fail. Is there a way to achieve that with JavaScriptSerializer? And yes, I agree that Json.Net is great, but now my question was specific about not using third party libraries :) – qqbenq Aug 26 '14 at 14:53