34

I have the following JSON text:

{
    "PropOne": {
        "Text": "Data"
    }
    "PropTwo": "Data2"
}    

I want to deserialize PropOne into type PropOneClass without the overhead of deserializing any other properties on the object. Can this be done using JSON.NET?

Omar
  • 39,496
  • 45
  • 145
  • 213

6 Answers6

45

The JSON isn't too large, so I'll take Matt Johnson's suggestion and deserialize the whole thing. Thanks to jcwrequests answer, I was able to use this method:

var jObject = JObject.Parse(json);
var jToken = jObject.GetValue("PropTwo");
PropTwoClass value = jToken.ToObject(typeof(PropTwoClass));
Omar
  • 39,496
  • 45
  • 145
  • 213
35
public T GetFirstInstance<T>(string propertyName, string json)
{
    using (var stringReader = new StringReader(json))
    using (var jsonReader = new JsonTextReader(stringReader))
    {
        while (jsonReader.Read())
        {
            if (jsonReader.TokenType == JsonToken.PropertyName
                && (string)jsonReader.Value == propertyName)
            {
                jsonReader.Read();

                var serializer = new JsonSerializer();
                return serializer.Deserialize<T>(jsonReader);
            }
        }
        return default(T);
    }
}

public class MyType
{
    public string Text { get; set; }
}

public void Test()
{
    string json = "{ \"PropOne\": { \"Text\": \"Data\" }, \"PropTwo\": \"Data2\" }";

    MyType myType = GetFirstInstance<MyType>("PropOne", json);

    Debug.WriteLine(myType.Text);  // "Data"
}

This approach avoids having to deserialize the entire object. But note that this will only improve performance if the json is significantly large, and the property you are deserializing is relatively early in the data. Otherwise, you should just deserialize the whole thing and pull out the parts you want, like jcwrequests answer shows.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • Updated slightly for better re-usability. – Matt Johnson-Pint Oct 17 '13 at 23:45
  • 3
    This method is broken in that it will catch the first property "PropOne" anywhere at any level, not just in the root of the tree. It can be fixed by keeping track of the level and only accept the correct property name at the root level. – hultqvist Aug 13 '16 at 13:35
  • Note that this can also be useful if you're not sure what the JSON string you're decoding is, and you need to read a property to figure it out. – starbeamrainbowlabs Jan 27 '17 at 20:37
16

A simpler solution to Omar's answer would be to have a wrapper.

class Wrapper
{
    public PropOneClass PropOne;
}

JsonConvert.Deserialize<Wrapper>(json).PropOne

My tests found it to be about 30% faster.

Community
  • 1
  • 1
hultqvist
  • 17,451
  • 15
  • 64
  • 101
  • This is a much more flexible approach. Create classes that only have the properties you want to deserialize and it will skip all the bulk on the fly and it runs much faster. – PaulMolloy Jan 15 '20 at 09:26
7
 var json = "{ "PropOne": { "Text": "Data" } "PropTwo": "Data2" }";

 JObject o = JObject.Parse(json);
 var val = o.PropTwo;

Using JSON Linq provider you do not need to deserialize the object into a known type.

jcwrequests
  • 1,132
  • 1
  • 7
  • 13
2

Matt's answer is by far the fastest solution though it has a bug. This is my attempt in fixing that. This method will only return a matching property at the root level. There is still a naive approach in counting start and end tokens though for valid JSON it will probably work.

Matt, feel free to copy this into your answer.

public T GetFirstInstance<T>(string propertyName, string json)
{
    using (var stringReader = new StringReader(json))
    using (var jsonReader = new JsonTextReader(stringReader))
    {
        int level = 0;

        while (jsonReader.Read())
        {
            switch (jsonReader.TokenType)
            {
                case JsonToken.PropertyName:
                    if (level != 1)
                        break;
                    if ((string)jsonReader.Value == propertyName)
                    {
                        jsonReader.Read();

                        return (T)jsonReader.Value; 
                    }
                    break;

                case JsonToken.StartArray:
                case JsonToken.StartConstructor:
                case JsonToken.StartObject:
                    level++;
                    break;

                case JsonToken.EndArray:
                case JsonToken.EndConstructor:
                case JsonToken.EndObject:
                    level--;
                    break;
            }

        }
        return default(T);
    }
}
Community
  • 1
  • 1
hultqvist
  • 17,451
  • 15
  • 64
  • 101
  • 2
    How about skipping the token's children so we only get root level nodes: `while (jsonReader.Read()) { if ((string)jsonReader.Value == propertyName) { //return } jsonReader.Skip(); }` – amolbk Aug 28 '18 at 08:02
  • @amolbk yes [Skip](https://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_JsonReader_Skip.htm) looks like it was added [6 months ago](https://www.nuget.org/packages/Newtonsoft.Json/) and would simplify the code. Perhaps that is better for a new answer. – hultqvist Aug 29 '18 at 11:29
1

Use JsonIgnore - this will cause the property to be completely ignored by Json.Net, both for serializing and deserializing.

Also, check this link.

NomadTraveler
  • 1,086
  • 1
  • 12
  • 37
  • I don't have a composite object that contains both `PropOne` and `PropTwo`, rather I have two separate classes that represent `PropOne` and `PropTwo`. – Omar Oct 17 '13 at 23:27
  • Are you serializing the objects too? – NomadTraveler Oct 17 '13 at 23:38
  • If by that you mean I'm the one creating the JSON, then no, the JSON I'm getting is sent by a 3rd party. – Omar Oct 17 '13 at 23:41
  • NomadTraveler, do you know if it is possible to have JsonIgnore only for deserializing? – Zwik Apr 21 '14 at 21:21