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:
- 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. - When you take the
Children()
of theJObject
, use the overload that lets you cast them toJProperty
objects; that will make it much easier to extract the data you want. - Get the
culture
from theName
of theJProperty
item - To get the
urls
, get theValue
of theJProperty
item and useToObject<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
How to Post Using Httpclient Content Type = Application/X-Www-Form-Urlencoded
Best .Net Memory and Performance Profiler
Is There a Standard Way to Encode a .Net String into JavaScript String for Use in Ms Ajax
Seeking Clarification on Apparent Contradictions Regarding Weakly Typed Languages
What Is the Connection String for Localdb for Version 11
Ef Code-First One-To-One Relationship: Multiplicity Is Not Valid in Role * in Relationship
Where Does Error Cs0433 "Type 'X' Already Exists in Both A.Dll and B.Dll " Come From
How to Get Timestamp of Tick Precision in .Net/C#
What Are Major Differences Between C# and Java
Difference Between Namespace in C# and Package in Java
What Exactly Happens When I Set Loaduserprofile of Iis Pool
What Is the "Volatile" Keyword Used For
How to Lock a Table on Read, Using Entity Framework
Classes Residing in App_Code Is Not Accessible
Automapper: Update Property Values Without Creating a New Object
How to Log the Generated SQL from Dbcontext.Savechanges() in My Program