Custom Deserialization Using JSON.Net

Custom Deserialization using Json.NET

I just resolve my problem using JsonConverter as I mentioned above in my question. Below my complete code:

public class Order
{
public int Id { get; set; }

[JsonConverter(typeof(ShippingMethodConverter))]
public string ShippingMethod { get; set; }
}

public class ShippingMethodConverter : JsonConverter
{

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Not implemented yet");
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return string.Empty;
}
else if (reader.TokenType == JsonToken.String)
{
return serializer.Deserialize(reader, objectType);
}
else
{
JObject obj = JObject.Load(reader);
if (obj["Code"] != null)
return obj["Code"].ToString();
else
return serializer.Deserialize(reader, objectType);
}
}

public override bool CanWrite
{
get { return false; }
}

public override bool CanConvert(Type objectType)
{
return false;
}
}

C# Newtonsoft.Json Custom Deserializer

You can do this with a generic custom JsonConverter such as the following:

public class WrapWithValueConverter<TValue> : JsonConverter
{
// Here we take advantage of the fact that a converter applied to a property has highest precedence to avoid an infinite recursion.
class DTO { [JsonConverter(typeof(NoConverter))] public TValue value { get; set; } public object GetValue() => value; }

public override bool CanConvert(Type objectType) => typeof(TValue).IsAssignableFrom(objectType);

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=> serializer.Serialize(writer, new DTO { value = (TValue)value });

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> serializer.Deserialize<DTO>(reader)?.GetValue();
}

public class NoConverter : JsonConverter
{
// NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
// By https://stackoverflow.com/users/3744182/dbc
// To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter
public override bool CanConvert(Type objectType) { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }
public override bool CanRead => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}

Then you can apply it to your model as follows:

class Customer {
[JsonConverter(typeof(WrapWithValueConverter<string>))]
public string CustomerID { get; set; }
[JsonConverter(typeof(WrapWithValueConverter<string>))]
public string CustomerCurrencyID { get; set; }
}

Demo fiddle #1 here.

Or, if you want all strings to be wrapped in a {"value": <string value>} object, you can add the converter to JsonSerializerSettings.Converters when serializing and deserializing:

var settings = new JsonSerializerSettings
{
Converters = { new WrapWithValueConverter<string>() },
};

var model = JsonConvert.DeserializeObject<Customer>(json, settings);

var json2 = JsonConvert.SerializeObject(model, Formatting.Indented, settings);

Demo fiddle #2 here.

If your value is an enum and you want to serialize it as a string, you can replace NoConverter with StringEnumConverter by using the following:

public class WrapEnumWithValueConverter<TEnum> : JsonConverter where TEnum: Enum
{
// Here we take advantage of the fact that a converter applied to a property has highest precedence to avoid an infinite recursion.
class DTO { [JsonConverter(typeof(StringEnumConverter))] public TEnum value { get; set; } public object GetValue() => value; }

public override bool CanConvert(Type objectType) => typeof(TEnum).IsAssignableFrom(objectType);

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=> serializer.Serialize(writer, new DTO { value = (TEnum)value });

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> serializer.Deserialize<DTO>(reader)?.GetValue();
}

Demo fiddle #3 here.

Using a Custom Json Deserializer to create a new object

You can write a simple JsonConverter for the GeographyPoint type as shown below. This implementation uses a JObject as a means to easily read from (and write to) the JSON. The key to creating the GeographyPoint instance is using the static Create method provided by that type.

class GeographyPointConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(GeographyPoint).IsAssignableFrom(objectType);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken obj = JToken.Load(reader);
if (obj.Type == JTokenType.Null) return null;
double latitude = (double)obj["Latitude"];
double longitude = (double)obj["Longitude"];
return GeographyPoint.Create(latitude, longitude);
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
GeographyPoint point = (GeographyPoint)value;
JObject obj = new JObject(
new JProperty("Latitude", new JValue(point.Latitude)),
new JProperty("Longitude", new JValue(point.Longitude))
);
obj.WriteTo(writer);
}
}

To use the converter, just add a [JsonConverter] attribute to the Location property in your LookUpData class:

public class LookUpData
{
public string Id { get; set; }

[JsonConverter(typeof(GeographyPointConverter))]
public GeographyPoint Location { get; set; }
}

Note: you don't actually need the [DataContract] and [DataMember] attributes when using Json.Net, unless you need opt-in semantics — meaning you need to specifically identify which classes and members are included in serialization and deserialization. If these attributes are omitted, then all classes and members are included by default, which seems to be your intent here.

Working demo: https://dotnetfiddle.net/YNo1vP

Custom deserializer only for some fields with json.NET

Since you are annotating your type with Json.NET attributes anyway, a simpler solution would seem to be to put the converters on the relevant properties using [JsonConverter(Type)] or [JsonProperty(ItemConverterType = Type)]:

public class Configuration
{
public int a { get; set; }
public int b { get; set; }
public Obj1 obj1 { get; set; }

// Converts the entire list to a compressed string
[JsonConverter(typeof(IntListConverter))]
public int[] c { get; set; }

// Converts each Obj2 item individually
[JsonProperty(ItemConverterType = typeof(Obj2Converter))]
public IList<Obj2> obj2 { get; set; }
}

Nevertheless, if you need to retain the converter on Configuration (or are actually adding the converter to JsonSerializerSettings.Converters and cannot add Json.NET attributes to your type), you can use JsonSerializer.Populate() to populate the standard properties, as long as you first remove the custom properties from the JObject:

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;

var jsonObject = JObject.Load(reader);

var configuration = (existingValue as Configuration ?? new Configuration());

// I created the JsonConverter for those 2 properties
configuration.c = myCustomProcessMethod(jsonObject["c"].RemoveFromLowestPossibleParent());
configuration.obj2 = myCustomProcessMethod2(jsonObject["obj2"].RemoveFromLowestPossibleParent().ToObject<ValletConfiguration>());

// Populate the remaining standard properties
using (var subReader = jsonObject.CreateReader())
{
serializer.Populate(subReader, configuration);
}

return configuration;
}

Using the extension method:

public static class JsonExtensions
{
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (contained != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
}

Custom deserialization of property in json.net

I think, there is no option except implementing custom converter;

class CollectionConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}

public override bool CanWrite => false;

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("There is no writing implemantation");
}
}

public class PersonList
{
[JsonConverter(typeof(CollectionConverter<Person>))]
public List<Person> Persons { get; set; }
}

Deserialize object as collection;

var str = "{ persons: { name: \"lastname1, firstname1\" } }";
var obj = JsonConvert.DeserializeObject<PersonList>(str);

Deserialize collection;

var str = "{ persons: [{ name: \"lastname1, firstname1\" },{ name: \"lastname1, firstname1\" }] }";
var obj = JsonConvert.DeserializeObject<PersonList>(str);

Custom JSON Deserialization in C# with JsonConverter

It seems like you only want to perform custom serialization of TimeSpan as it belongs to Ownership, so why not make a converter for TimeSpan only and save yourself from manually serializing all of the other class properties?:

public class TimeSpanConverter : JsonConverter<TimeSpan>
{
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return TimeSpan.FromTicks(reader.GetInt64());
}

public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value.Ticks);
}
}

Then decorate your MeanInterval property with a JsonConverterAttribute:

public class Ownership
{
public string OwnershipId { get; set; }
public List<string> TextOutput { get; set; }
public DateTime DateTime { get; set; }
[JsonConverter(typeof(TimeSpanConverter))]
public TimeSpan MeanInterval { get; set; }// Like long ticks, TimeSpan.FromTicks(Int64), TimeSpan.Ticks
}

Try it online

Json.NET: deserialize any kind of object

You can create a custom deserializer :
https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm

public class DescriptionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return false;
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
{
//If is string, return the string
return serializer.Deserialize(reader, objectType);
}
else
{
//If not string, try get the field '$ref'
var obj = JObject.Load(reader);
if (obj["$ref"] != null)
return obj["$ref"].ToString();
else
throw new InvalidOperationException("Invalid Json");
}
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Then you can specify this converter in your model :

internal class Baz
{
[JsonProperty("tags")]
internal Tags Tags;
}

internal class Tags : Dictionary<string, Tag>
{
}

internal class Tag
{
[JsonProperty("description")]
[JsonConverter(typeof(DescriptionConverter))]
internal string Description;
}

Finally, you can deserialize the json :

static void Main(string[] args)
{
string json = @"{
'tags': {
't1': {
'description': 'bar'
},
't2': {
'description': {
'$ref': './t2.md'
}
}
}
}";
var baz = JsonConvert.DeserializeObject<Baz>(json);
Console.WriteLine("t1 : " + baz.Tags["t1"].Description);
Console.WriteLine("t2 : " + baz.Tags["t2"].Description);
}

Output :

t1 : bar
t2 : ./t2.md

Deserializing JSON using custom deserializer with JSON.Net

You're on the right track. Here are the corrections you need to make:

  1. You're iterating over children of the top-level object expecting to get data that is actually in an object one level further down. You need to navigate to the value of the MobileSiteContents property and iterate over the children of that.
  2. When you take the Children() of the JObject, use the overload that lets you cast them to JProperty objects; that will make it much easier to extract the data you want.
  3. Get the culture from the Name of the JProperty item
  4. To get the urls, get the Value of the JProperty item and use ToObject<string[]>() to convert it to a string array.

Here is the corrected code:

public IEnumerable<MobileSiteContentsContentSectionItem> Parse(string json)
{
var jObject = JObject.Parse(json);

var result = new List<MobileSiteContentsContentSectionItem>();

foreach (var item in jObject["MobileSiteContents"].Children<JProperty>())
{
var culture = item.Name;
string[] urls = item.Value.ToObject<string[]>();

result.Add(new MobileSiteContentsContentSectionItem { Culture = culture, Urls = urls });
}

return result;
}

If you like terse code, you can reduce this to a "one-liner":

public IEnumerable<MobileSiteContentsContentSectionItem> Parse(string json)
{
return JObject.Parse(json)["MobileSiteContents"]
.Children<JProperty>()
.Select(prop => new MobileSiteContentsContentSectionItem
{
Culture = prop.Name,
Urls = prop.Value.ToObject<string[]>()
})
.ToList();
}

Demo:

class Program
{
static void Main(string[] args)
{
string json = @"
{
""MobileSiteContents"": {
""au/en"": [
""http://www.url1.com"",
""http://www.url2.com"",
],
""cn/zh"": [
""http://www.url2643.com"",
]
}
}";

foreach (MobileSiteContentsContentSectionItem item in Parse(json))
{
Console.WriteLine(item.Culture);
foreach (string url in item.Urls)
{
Console.WriteLine(" " + url);
}
}
}

public static IEnumerable<MobileSiteContentsContentSectionItem> Parse(string json)
{
return JObject.Parse(json)["MobileSiteContents"]
.Children<JProperty>()
.Select(prop => new MobileSiteContentsContentSectionItem()
{
Culture = prop.Name,
Urls = prop.Value.ToObject<string[]>()
})
.ToList();
}

public class MobileSiteContentsContentSectionItem : ContentSectionItem
{
public string[] Urls { get; set; }
}

public abstract class ContentSectionItem
{
public string Culture { get; set; }
}
}

Output:

au/en
http://www.url1.com
http://www.url2.com
cn/zh
http://www.url2643.com


Related Topics



Leave a reply



Submit