Using Newtonsoft Json how cast an int value to string - c#

I'm just starting to check how to serialize and deserialize using json for a project where I need to use ArangoDB.
At the moment, I have a test class AnoherTestPerson:
public class AnotherTestPerson
{
public AnotherTestPerson(int id, string fullname, int age)
{
this.Id = id;
this.Fullname = fullname;
this.Age = age;
}
public int Id { get; set; }
public string Fullname { get; set; }
public int Age { get; set; }
}
Now, I need to cast the Id value to a string, because ArangoDB doesn't work when you pass a numerical value as the _key, so I'm guessing I have to do that from the serializer that the Arango driver uses, because in the project I'm going to work on, we won't have access to the classes of the entities we want to store on the data base.
Any help would be appreciated, as I'm still learning how serialization works with Json and C#.
Here's the rest of the code:
public static async Task Main(string[] args)
{
string connectionString = "private";
var arango = new ArangoContext(cs:connectionString, settings:
new ArangoConfiguration
{
Serializer = new ArangoNewtonsoftSerializer(CustomDataContractResolver.Instance)
//Using custom contract resolver for automatically changing the Id name
//from the object class to _key in the Json file
}
);
await arango.Document.CreateAsync("TestDB", typeof(AnotherTestPerson).Name, testPerson);
}
Here's the custom contract resolver. I tried changing the type of the property here but it didn't work.
public class CustomDataContractResolver : DefaultContractResolver
{
public static readonly CustomDataContractResolver Instance = new CustomDataContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyName.Equals("Id", StringComparison.OrdinalIgnoreCase))
{
property.PropertyName = "_key";
if(property.PropertyType == Type.GetType("System.Int32"))
{
property.PropertyType = Type.GetType("System.String");
}
}
return property;
}
}
EDIT
So checking the comment by SBFrancies, I implemented a basic JsonConverter:
public class ToStringJsonConverted : Newtonsoft.Json.JsonConverter
{
public static readonly ToStringJsonConverted Instance = new ToStringJsonConverted();
public override bool CanConvert(Type objectType)
{
return true;
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
}
and linked it to the custom ContractResolver:
public class CustomDataContractResolver : DefaultContractResolver
{
public static readonly CustomDataContractResolver Instance = new CustomDataContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyName.Equals("Id", StringComparison.OrdinalIgnoreCase))
{
property.PropertyName = "_key";
if(property.PropertyType == Type.GetType("System.Int32"))
{
property.Converter = ToStringJsonConverted.Instance;
}
}
return property;
}
}
It get's serialized as I wanted to, but the deserializing it's not working right now. I'll check how to read Json files and parse them for now.

I got the serializing and deserializing working, with help from #SBFrancies reference in the comments.
ContractResolver:
public class CustomDataContractResolver : DefaultContractResolver
{
public static readonly CustomDataContractResolver Instance = new CustomDataContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyName.Equals("Id", StringComparison.OrdinalIgnoreCase))
{
property.PropertyName = "_key";
if(property.PropertyType == Type.GetType("System.Int32"))
{
property.Converter = ToStringJsonConverted.Instance;
}
}
return property;
}
}
JsonConverter:
public class ToStringJsonConverted : Newtonsoft.Json.JsonConverter
{
public static readonly ToStringJsonConverted Instance = new ToStringJsonConverted();
public override bool CanConvert(Type objectType)
{
return true;
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
return Int32.Parse((string)reader.Value);
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
}

Related

Newtonsoft C# - Custom DateTime conversion to Google.Protobuf.WellKnownTypes.Timestamp

My scenario is: I have to test GRPC calls. I have to get a JSON body and turn into a Proto object. When attributes are int32, string, etc it works perfectly fine. But when the type is TimeStamp, then the problem happens.
I wrote this code in Fiddler https://dotnetfiddle.net/H1U3i4:
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public class Program
{
public class MyProtobufObject
{
public Google.Protobuf.WellKnownTypes.Timestamp openingDatetime {get;set;}
}
public class TimeStampConverter : DateTimeConverterBase
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
DateTime date = DateTime.Parse(reader.Value.ToString());
return Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(date).ToString();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((Google.Protobuf.WellKnownTypes.Timestamp)value).ToString());
}
}
public static void Main()
{
string sDate = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(DateTime.UtcNow).ToString();
Console.WriteLine(sDate);
string myJsonBodyRequest = "{\"openingDatetime\":"+sDate+"}";
Console.WriteLine(myJsonBodyRequest);
MyProtobufObject myObjectWithConverter = JsonConvert.DeserializeObject<MyProtobufObject>(myJsonBodyRequest, new TimeStampConverter());
MyProtobufObject myObjectWithoutConverter = JsonConvert.DeserializeObject<MyProtobufObject>(myJsonBodyRequest);
}
}
Output is:
"2021-02-24T17:28:52.391136Z"
{"openingDatetime":"2021-02-24T17:28:52.391136Z"}
Unhandled exception. Newtonsoft.Json.JsonSerializationException: Error converting value 02/24/2021 17:28:52 to type 'Google.Protobuf.WellKnownTypes.Timestamp'. Path 'openingDatetime', line 1, position 48. ---> System.ArgumentException: Could not cast or convert from System.DateTime to Google.Protobuf.WellKnownTypes.Timestamp.
I also tried to implement a custom converter TimeStampConverter but no success.
What am I doing wrong?
This works:
public class TimeStampContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyType == typeof(Google.Protobuf.WellKnownTypes.Timestamp))
{
property.Converter = new TimeStampConverter();
}
return property;
}
public class TimeStampConverter : DateTimeConverterBase
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
DateTime date = DateTime.Parse(reader.Value.ToString());
date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
return Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(date);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((Google.Protobuf.WellKnownTypes.Timestamp)value).ToString());
}
}
}
Then to use it you do like this:
var settings = new JsonSerializerSettings
{
ContractResolver = new TimeStampContractResolver()
};
var myObj = JsonConvert.DeserializeObject<MyObject>(jsonString, settings);
It looks like you're going a little out of sequence. I find the easiest way is to provide a contract resolver instead of a converter to the JsonConvert.Deserialize<>(). As an example from a thousand feet:
var result = JsonConvert.DeserializeObject<Target>(source, new JsonSerializer()
{
ContractResolver = new YourTimestampContractResolver(),
};
// elsewhere... Inherting the DefaultContractResolver saves you some implementation
public class YourTimestampContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyType == typeof(DateTime))
{
property.Converter = new YourCustomDateTimeConverter();
}
return property;
}
}
This way is generally way more powerful as you can see any property type or name, which has generally covered most of my bases.

Access already deserialized properties from JsonConverter.ReadJson

I am trying to solve backward compartibility when deserializing old json. Previously there was double property and now it's changed to a custom type.
My idea is to read double and simply convert it using custom json converter.
Before was:
public class A
{
[JsonProperty)]
string Name { get; set; }
[JsonProperty)]
double Value { get; set; }
}
Serialized as
{"Name":"test","Value":33.0}
New one:
public class A
{
[JsonProperty]
[JsonConverter(typeof(MyJsonConverter))]
public MyType Value { get; set; }
}
Serialized as
{"Value":{"Value":33.0,"Name":"test", ...}},
Converter:
public class MyJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => true;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value is double value)
return new MyType(value, ???); // here is the problem, I need Name value here
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) =>
JToken.FromObject(value).WriteTo(writer);
}
but to construct MyType I need string parameter which is a value of another property Name
How to access Name value from converter for Value? How to access anything what has been deserialized? Is there some kind of tree? Tokens tree?
Another thing: in WriteJson method I want to do "nothing" special, is my implementation correct? Or is there an easy way to prevent converter doing anything "special" upon serialization?
You would need to apply a converter to your parent A class:
[JsonConverter(typeof(MyJsonConverter))]
public class A
{
public MyType Value { get; set; }
}
public class AConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(A);
public override bool CanWrite => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
// Check if the keys contains "Name"
string name = jObject["Name"]?.ToString();
var a = new A();
if (name != null)
{
a.Value = new MyType
{
Name = name,
Value = jObject["Value"].Value<double>()
};
}
else
{
a.Value = jObject["Value"].ToObject<MyType>();
}
return a;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use default serailisation, just override CanWrite with false.

How to move JSON object's depth one level up in JSON.net using C#?

I am trying to write a converter for a class using JSON.net
When I serialize the object of type JsonObject, I get the following output
{"DataObject":{"id":"1","name":"data name"}}
How can I move the DataObject one level up to get the following output:
{"id":"1","name":"data name"}
You can find the relevant code below.
My class has the following format:
public class JsonObject
{
public JsonObject(IDataObject dataObject)
{
this.DataObject= dataObject;
}
[JsonConverter(typeof(JsonDataObjectConverter))]
public IDataObject DataObject;
}
The DataObject has some properties:
public class MyDataObject : IDataObject
{
[JsonProperty(PropertyName = "id", Required = Required.Always)]
public string Id { get; set; }
[JsonProperty(PropertyName = "name", Required = Required.Always)]
public string Name { get; set; }
}
I referred this page from the documentation and wrote the following converter:
public class JsonDataObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IDataObject).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is MyDataObject dataObject)
{
writer.WriteStartObject();
writer.WritePropertyName("id");
writer.WriteValue(dataObject.Id);
writer.WritePropertyName("name");
writer.WriteValue(dataObject.Name);
writer.WriteEndObject();
}
}
}
Any help is appreciated. Thank you.
I was able to get the desired output by moving the converter to the class instead of the property and ignoring the property using [JsonIgnore]. The property needs to be ignored since the converter for the class will generate the JSON for the property as shown below.
So the JsonObject class will be:
[JsonConverter(typeof(JsonObjectConverter))]
public class JsonObject
{
public JsonObject(IDataObject dataObject)
{
this.DataObject= dataObject;
}
[JsonIgnore]
public IDataObject DataObject;
}
Then create the converter like this:
public class JsonObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IDataObject).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JsonObject jsonObject = (JsonObject)value;
if (jsonObject.DataObject.GetType() == typeof(MyDataObject))
{
MyDataObject dataObject = (MyDataObject) jsonObject.DataObject;
writer.WriteStartObject();
writer.WritePropertyName("id");
writer.WriteValue(dataObject.Id);
writer.WritePropertyName("name");
writer.WriteValue(dataObject.Name);
writer.WriteEndObject();
}
}
}
This gives the desired output of:
{"id":"1","name":"data name"}

How to Ignoring Fields and Properties Conditionally During Serialization Using JSON.Net?

How to Ignoring Fields and Properties Conditionally During Serialization Using JSON.Net?
I can't inherit from JsonIgnoreAttribute because it's a sealed class. What should I do?
You can use JSON.NET's ShouldSerialize-syntax. There's a good example on JSON.NET site:
http://www.newtonsoft.com/json/help/html/ConditionalProperties.htm
public class Employee
{
public string Name { get; set; }
public Employee Manager { get; set; }
public bool ShouldSerializeManager()
{
// don't serialize the Manager property if an employee is their own manager
return (Manager != this);
}
}
If ShouldSerialize doesn't fit your needs, you can take full control of the serialization with the ContractResolvers: http://www.newtonsoft.com/json/help/html/ContractResolver.htm
I found the answer. I inherit from JsonConverter and create a new convertor.
public class CustomJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var list = (IList)value;
JArray s = new JArray();
foreach (var item in list)
{
JToken token = JToken.FromObject(item);
JObject obj = new JObject();
foreach (JProperty prop in token)
{
if (prop.Name != "Title") // your logic here
obj.Add(prop);
}
s.Add(obj);
}
s.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType != typeof(IList);
}
}

How to remove a property during WriteJson

I have created a test console application that has a simple class named Other. As an exercise, I want to set the OtherString property to null when it is serialized. I know how I could do this with a custom ContractResolver. I need to be able to do this with a custom Converter too.
The first converter I wrote was simple and the way I thought it should be. However, it would throw a "Self referencing loop detected with type 'JsonContractandConvert.Models.Other'. Path ''." exception. After doing some reading, I made some changes and now I have a working converter. These changes are a lot more verbose, but work.
My question is why are these changes required and is there a better way to do this with a Converter?
Other Class:
[JsonConverter(typeof(OtherConverter))]
public class Other
{
public int Id { get; set; }
public string OtherString { get; set; }
public int OtherInt { get; set; }
public string OtherName
{
get
{
return "Other Name = " + this.OtherString;
}
}
}
First Attempt: (This one throws an exception)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!this.CanConvert(value.GetType())) return;
var entity = value as Other;
if (entity == null) return;
entity.OtherString = null;
serializer.Serialize(writer, entity);
}
Second Attempt: (This works as expected)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!this.CanConvert(value.GetType())) return;
var entity = value as Other;
if (entity == null) return;
entity.OtherString = null;
writer.WriteStartObject();
var props = entity.GetType().GetProperties();
foreach (var propertyInfo in props)
{
var ignorAttribute =
propertyInfo.CustomAttributes.FirstOrDefault(i => i.AttributeType == typeof(JsonIgnoreAttribute));
if (ignorAttribute != null) continue;
var tempVal = propertyInfo.GetValue(entity);
if (tempVal == null) continue;
writer.WritePropertyName(propertyInfo.Name);
serializer.Serialize(writer, tempVal);
}
}
writer.WriteEndObject();
}
Edit:
Here's the code from the console app that I'm using to test.
class Program
{
static void Main(string[] args)
{
var otherObj = new Other { Id = 123, OtherInt = 456, OtherString = "This is the other string"};
var json = JsonConvert.SerializeObject(otherObj, Formatting.Indented);
Console.WriteLine(json);
}
}
Answering my own questions... (I think I've finally got my head around Json Converters)
To answer my basic question as to why one way works and the other doesn't. I think the answer is just that the way that doesn't work is just wrong. From what I can tell, you need to use the writer object. If you don't it won't work. (There may be scenarios where this is not the case, but I never found one.)
If you merely want to remove a property from the Json, use [JsonIgnore]. (Simple right?)
If you want to change the value some how, you'll need to use a Converter. By using a Converter, you're taking responsibility for serializing what ever object the Converter handles. So if the object is a simple type like a String, or an Int it's really easy. If it's something more complex like an array or complex object, then it takes a little more planning.
Here's an example of a simple String Converter:
Class to serialize:
public class Account
{
public int Id { get; set; }
public string AccountName { get; set; }
[JsonIgnore]
public virtual Account DefaultAssignTo { get; set; }
public int? DefaultAssignToId { get; set; }
[JsonIgnore]
public virtual ICollection<Role> Roles { get; set; }
[JsonIgnore]
public virtual Other Other { get; set; }
public int? OtherId { get; set; }
[JsonConverter(typeof(StringConverter))]
public string OtherName
{
get
{
return "Name = " + this.AccountName;
}
}
}
Converter:
public class StringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(string).IsAssignableFrom(objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!this.CanConvert(value.GetType())) return;
writer.WriteValue("blah blah blah");
}
}
Run: (As you can see I'm using Entity Framework for my data)
using (var db = new Context())
{
var json = JsonConvert.SerializeObject(db.Accounts.FirstOrDefault(), Formatting.Indented,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
Console.WriteLine(json);
}
Returns:
{
"Id": 43,
"AccountName": "John",
"DefaultAssignToId": 43,
"OtherId": 19,
"OtherName": "blah blah blah"
}
One of the things that messed me up for a bit was when to use writer.WriteStartObject(). Basically if you are Converting a complex object, you need to use it. If you do, then you need to create all of the property names and values. In the OP you can see an example of how I did this. The biggest down side is any Json attributes that properties are decorated with don't automatically happen. So if you can, decorate the class and let the serializer deal with it. Here's an interesting problem you can run into if you use it wrong.
In the String converter replace:
writer.WriteValue("blah blah blah");
with:
writer.WriteStartObject();
writer.WritePropertyName("BlahProp");
serializer.Serialize(writer, "blah blah blah");
writer.WriteEndObject();
run it again and here's the output: (Notice how OtherName is an object now instead of a string)
{
"Id": 43,
"AccountName": "John",
"DefaultAssignToId": 43,
"OtherId": 19,
"OtherName": {
"BlahProp": "blah blah blah"
}
}
The Roles collection also needed a Converter. Two things to note are first, you don't need to specify the property name and second, any Json attributes that the Role class is decorated with will work as expected.
using BaseCollection = System.Collections.Generic.ICollection<JsonContractandConvert.Models.Role>;
public class RemoveAccountsFromRolesConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(BaseCollection).IsAssignableFrom(objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!this.CanConvert(value.GetType())) return;
var entities = value as BaseCollection;
if (entities == null) return;
writer.WriteStartArray();
foreach (var entity in entities)
{
entity.Accounts = null;
serializer.Serialize(writer, entity);
}
writer.WriteEndArray();
}
}
Lastly, here's a couple of converters I created for my real project:
This one converts an object to a shallow copy. This is useful if you have a collection where what that collection contains could cause a self referencing loop.
public class ShallowCopyCollectionConverter<TCollectionType, TCopyType> : JsonConverter
where TCollectionType : IEnumerable<TbdEntity>
where TCopyType : TbdEntity, new()
{
public override bool CanConvert(Type objectType)
{
return typeof(TCollectionType).IsAssignableFrom(objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (this.CanConvert(value.GetType()) == false) return;
var entities = (TCollectionType)value;
writer.WriteStartArray();
foreach (var entity in entities)
{
serializer.Serialize(writer, entity.ShallowCopy<TCopyType>()); //ShallowCopy<> is a method in the base class that all of my classes extend.
}
writer.WriteEndArray();
}
}
This one will convert an abstract using the concrete type:
public class DataSnapInConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(DataSnapIn) == (objectType);
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (this.CanConvert(objectType) == false) return null;
var jo = JObject.Load(reader);
var typeName = jo["snapInType"] ?? jo["SnapInType"]; //the abstract classes have this property to identify what concrete class they are.
var typeNameString = typeName.ToString();
var deserializeType = Type.GetType(typeNameString);
if(deserializeType == null)
throw new Exception("SnapInType is null or does not reference a valid class.");
var result = Activator.CreateInstance(deserializeType);
serializer.Populate(jo.CreateReader(), result);
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Categories

Resources