When the reader is positioned at a value, you can call JsonSerializer.Deserialize(), JToken.Load(), JArray.Load() or JObject.Load() to deserialize or load that value. Generally I prefer doing that to making manual "atomic" reads, because it ensures that nesting is handled correctly and leaves the reader correctly positioned for the next item in the JSON stream. Calling JsonSerializer.Deserialize with the appropriate type when positioned on a property value gives you the default implementation of deserialization for that property.
In fact, a simple way to write a ReadJson method is to load the entire contents of the object into a JObject, then iterate through the properties:
var obj = JObject.Load(reader); foreach (var property in obj.Properties()) { switch (property.Name) { case "Data2": { // For example myClass.Data2 = obj.ToObject<List<double>>(serializer); } break; // More cases as necessary default: Debug.WriteLine("Unknown property " + property); break; } }
If for whatever reason you don't want to do that, you can read property-by-property and deserialize or load individual values:
while (reader.Read() && reader.TokenType != JsonToken.EndObject) { if (reader.TokenType == JsonToken.PropertyName) { var propertyName = reader.Value.ToString(); if (reader.Read()) { switch (propertyName) { case "Data1": { // Expecting a double value. if (reader.TokenType == JsonToken.Integer || reader.TokenType == JsonToken.Float) myClass.Data1 = Convert.ToDouble(reader.Value); else { // Unknown value, skip or throw an exception JToken.Load(reader); } } break; case "Data2": { // For example myClass.Data2 = serializer.Deserialize<List<double>>(reader); } break; case "Data3": { // Expecting a string value. myClass.Data3 = JToken.Load(reader).ToString(); } break; default: { // Unknown property, skip or throw an exception var unknownValue = JToken.Load(reader); Debug.WriteLine(string.Format("Uknown property \"{0}\" with value: {1}", propertyName, unknownValue)); } break; } } } }