I have a set of complex business objects that I'd like to serialize to Json for use in a web service. I'm currently using the DataContractJsonSerializer to product the Json, but it balks on deserialization because the default XmlReader can't handle Base64 strings.
After reading many positive reviews of Json.Net, I decided to give it a try. Surprisingly, the simplest case produces erroneous output if the business object overrides the ToString() method. Instead of generating JSON, it simply emits the string value.
For example, the following statement produces only a string, as the serializer appears to view the object as a simple string.
public class MyClass {
public string Title{get;set;}
public override ToString(){ return Title; }
public string ToJson(){
return JsonConvert.SerializeObject(this);
}
}
Instead of json formatted output, all I get is the title string. Do I have to mark the object in some special way to avoid this? Since the business object hierarchy includes many objects that override ToString(), I would rather avoid having to introduce special attributes, etc.
Is it possible that your actual class has a TypeConverterAttribute attached to it? I just ran into the exact same problem and found out that the TypeConverterAttribute was causing this. In thast case, this might help (at least it did for me).
This is very bad because you can inadvertently break your program (by simply adding a TypeConverter maybe for displaying the object in a PropertyGrid) without getting a compiler warning...
using Newtonsoft.Json;
using System;
using System.ComponentModel;
namespace ConsoleApplication5
{
class Program
{
static void Main(string[] args)
{
var with = new WithTypeConverter() { Bla = 12, Blub = "With" };
var without = new WithoutTypeConverter() { Bla = 12, Blub = "Without" };
Console.WriteLine(with);
Console.WriteLine(JsonConvert.SerializeObject(with));
Console.WriteLine(without);
Console.WriteLine(JsonConvert.SerializeObject(without));
Console.ReadKey();
}
}
public class baseClass
{
public int Bla { get; set; }
public string Blub { get; set; }
public override string ToString()
{
return String.Format("{0}: {1}", this.GetType().Name, Blub);
}
}
[TypeConverter(typeof(ExpandableObjectConverter))]
public class WithTypeConverter : baseClass
{
}
public class WithoutTypeConverter : baseClass
{
}
}
You may be testing this wrong. I just ran the following code in LINQPad:
void Main()
{
new MyClass{Title = "hi"}.ToJson().Dump();
}
// Define other methods and classes here
public class MyClass {
public string Title{get;set;}
public override string ToString(){ return Title; }
public string ToJson(){
return JsonConvert.SerializeObject(this);
}
}
Output:
{"Title":"hi"}
I suspect you are using MyClass as the key in a dictionary or hashtable?
Linqpad example:
void Main()
{
object thing = new Dictionary<MyClass, MyClass>() {
{
new MyClass { Title = "hi" }, new MyClass { Title = "bye" }
}
};
JsonConvert.SerializeObject(thing).Dump();
}
public class MyClass
{
public string Title { get; set; }
public override string ToString() { return "BROKEN"; }
}
Output:
{"BROKEN":{"Title":"bye"}}
This is the expected behaviour as there is no way to express a complex object as a key in json.
To work around this either change your model or implement a TypeConverter.
If your object is simple enough you could have the ConvertTo and ConvertFrom simply read and write parameters in a given order.
[EDIT]
This turned out to be more simple than I'd expected. Here is my JsonConverter solution.
public class ObjectKeyDictionaryTypeConverter<T1, T2> : JsonConverter<Dictionary<T1, T2>>
{
public override void WriteJson(JsonWriter writer, Dictionary<T1, T2> value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToArray());
}
public override Dictionary<T1, T2> ReadJson(JsonReader reader, Type objectType, Dictionary<T1, T2> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var items = serializer.Deserialize(reader) as KeyValuePair<T1,T2>[];
return items?.ToDictionary(a => a.Key, a => a.Value);
}
}
Usage:
[JsonConverter(typeof(ObjectKeyDictionaryTypeConverter<ICriteria, Results>))]
public Dictionary<ICriteria, Results> SearchesAndResults { get; set; }
Use JsonSerializer from System.Text.Json to serialize the class. Like so:
using System.Text.Json;
...
public class Foo{
Public String Title {get;set;}
public override ToString(){
return JsonSerializer.Serialize<Foo>(this);
}
}
Docs:
https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializer?view=netcore-3.1
Context:
https://youtu.be/JfnTG955cuk?t=406
Related
So, what I want is to serialise and deserialise some JSON using a generic field wrapper class.
I do not want to write some custom JsonConverter class.
I do wish to keep it simple and write some implicit operator type conversion. The following minimal piece of C# code is where I am at now .... just enough to demonstrate the issue and no more. Don't overlook the implicit operators.
using Newtonsoft.Json;
using System;
namespace test
{
public struct SofT<T>
{
[JsonProperty]
public T TValue { get; set; }
public static implicit operator SofT<T>(string jtoken)
{
return new SofT<T>()
{
TValue = (T)Convert.ChangeType(jtoken, typeof(T))
};
}
public static implicit operator string(SofT<T> soft)
{
return soft.TValue?.ToString() ?? "";
}
}
[JsonObject(MemberSerialization.OptIn)]
public class Something
{
[JsonProperty]
public SofT<int> TestStructInt { get; set; }
[JsonProperty]
public SofT<decimal> TestStructDecimal { get; set; }
}
public class Program
{
public void Run()
{
var json = "{ \"TestStructInt\" : \"12\", \"TestStructDecimal\" : \"3.45\"}";
var modelDeserialised = JsonConvert.DeserializeObject<Something>(json);
var modelReserialised = JsonConvert.SerializeObject(modelDeserialised);
Console.WriteLine(modelReserialised);
}
static void Main(string[] args)
{
new Program().Run();
}
}
}
The JSON string is deserialised perfectly.
The model object is not reserialised correctly.
The string that is spat out to the console is:
quote {"TestStructInt":{"TValue":12},"TestStructDecimal":{"TValue":3.45}}
The string I expect, or better put want, to be spat out to the console is the same structure in the source JSON, ie:
quote {"TestStructInt":"12"},"TestStructDecimal":"3.45"}}
I am asking for a second pair of eyes to point out my error (and yes I can see the error, the annotation of Value with [JsonProperty], but it seems necessary for default serialisation).
public class DynamicDictionary : System.Dynamic.DynamicObject
{
Dictionary<string, object> dictionary = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string name = binder.Name;
return dictionary.TryGetValue(name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
dictionary[binder.Name] = value;
return true;
}
public override System.Collections.Generic.IEnumerable<string> GetDynamicMemberNames()
{
return this.dictionary?.Keys;
}
}
public class SerializeExtensions
{
public string Name { get; set; }
public DynamicDictionary DynamicObj { get; set; }
}
Please consider the above like my class.
I have a List of SerializeExtensions collection. Now I have to deep clone this list of collections.
This has been properly cloned when I am using the below code.
JsonConvert.DeserializeObject<List<SerializeExtensions>>(JsonConvert.SerializeObject(collection));
This serialization belongs to Newtonsoft.Json. But I need to System.Text.Json for serialization.
So when I am using System.Text.Json serialization, the DynamicObject field has reset to empty. the below code has used.
JsonSerializer.Deserialize<List<SerializeExtensions>>(JsonSerializer.Serialize(collection));
After the serialization, I am getting the output like below
Name: "John"
DynamicObj: {}
Actually, the DynamicObj count is 4 in my case before serialization.
is there any possible to deep clone this kind of class?
I am serializing classes using JSON.net and using TypeNameHandling=Auto to insert the "$type" annotation. For the sake of the example, let's say this is my class:
[JsonObject]
public class MyClass
{
[JsonProperty]
public ISomeInterfaceA MyA { get; set; }
[JsonProperty]
public ISomeInterfaceB MyB { get; set; }
}
With this, both "MyA" and "MyB" receive the "$type" attribute. Unfortunately, I am in a weird situation where I need "MyA" to have "$type" but "MyB" should not have "$type". Does json.net have anything that would allow me to customize the behaviour of TypeNameHandling.Auto so that I can manually choose where I want it or not?
As for why I would want to do such a thing, the complicated reason is that I am migrating to JSON.net from another legacy serializer, and I am attempting to minimize the differences between the old and the new serializer to avoid having to rewrite large amounts of javascript code consuming the JSON.
You can achieve this using a custom converter.
Say this is your class:
[JsonObject]
public class MyClass {
[JsonProperty]
public ISomeInterfaceA MyA { get; set; }
[JsonProperty]
[JsonConverter(typeof(NoTypeConverter))]
public ISomeInterfaceA MyB { get; set; }
}
We need a custom converter:
public class NoTypeConverter: JsonConverter<ISomeInterfaceA> {
public override ISomeInterfaceA ReadJson(JsonReader reader, Type objectType, ISomeInterfaceA existingValue, bool hasExistingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, ISomeInterfaceA value, JsonSerializer serializer)
{
// Note: this is not the most efficient way to do this, but you can customise this how you see fit.
writer.WriteRawValue(JsonConvert.SerializeObject(value));
}
public override bool CanWrite => true;
public override bool CanRead => false;
}
Then:
var stuff = JsonConvert.SerializeObject(new MyClass
{ MyA = new A { Banana = "cheese" },
MyB = new A { Banana = "notype"} },
new JsonSerializerSettings{ TypeNameHandling=TypeNameHandling.Auto });
Console.WriteLine(stuff); // {"MyA":{"$type":"whatever, here","Banana":"cheese"},"MyB":{"Banana":"notype"}}
Note, however, that you can not deserialize BACK to your type this way because the type info is missing for MyB
var x = JsonConvert.DeserializeObject<MyClass>(stuff,
new JsonSerializerSettings{ TypeNameHandling=TypeNameHandling.Auto });
that will throw
Unhandled exception. Newtonsoft.Json.JsonSerializationException: Could not create an instance of type ISomeInterfaceA. Type is an interface or abstract class and cannot be instantiated. Path 'MyB.Banana', line 1, position 69.
I'm trying to deserialize a piece of XML that specifies a .NET type to an instance of System.Type. Given
<SomeObject>
<SomeType>System.String, mscorlib</SomeType>
</SomeObject>
To deserialize to a class;
public class SomeObject
{
public Type SomeType { get; set; }
}
Annoyingly, I've actually done this before a while back but without access to that source code and not being able to remember, this has proven very difficult to research the solution given the keywords needed ("Xml", "Deserialize", "Type" gives pretty much everything under the sun).
From what I remember, there is a simple Attribute that I put on the SomeType property and the XmlSerializer takes care of it from there. Does anyone know what attribute I need or am I mis-remembering?
If you don't want to have additional property of type string (usual solution to this problem) - you can use proxy class like this:
public class XmlTypeProxy : IXmlSerializable {
private string _typeName;
public XmlTypeProxy() {
}
public XmlTypeProxy(string typeName) {
_typeName = typeName;
}
public XmlSchema GetSchema() {
return null;
}
public void ReadXml(XmlReader reader) {
_typeName = reader.ReadString();
}
public void WriteXml(XmlWriter writer) {
writer.WriteString(_typeName);
}
public static implicit operator Type(XmlTypeProxy self) {
return Type.GetType(self._typeName);
}
public static implicit operator XmlTypeProxy(Type self) {
return new XmlTypeProxy(self.AssemblyQualifiedName);
}
}
What this class does is just stores type assembly qualified name as string and defines implicit conversion operator from and to Type type. Then you just need to decorate SomeType with XmlElement attribute and specify it's Type is XmlTypeProxy:
public class SomeObject {
[XmlElement(Type = typeof(XmlTypeProxy))]
public Type SomeType { get; set; }
}
Now, because there is implicit converstion from Type to XmlTypeProxy (and visa versa) - both serialization and deserialization will work as you expect.
While I've marked Evk's answer as correct (and it is if you are deserializing more than one Type property), I actually went with a simpler approach in the end.
Based on this answer, I modified by SomeObject to;
public class SomeObject
{
public string SomeTypeName
{
get { return SomeType.AssemblyQualifiedName; }
set
{
var converter = new TypeNameConverter();
SomeType = (Type)converter.ConvertFrom(value);
}
}
[XmlIgnore]
public Type SomeType { get; set; }
}
While a shorter piece of code for a single property, it's not as robust as the accepted answer. I'm recording here has the two approaches may help others.
I would like to serialize all nulls from string and nullable types to empty strings. I looked into using a custom JsonConverter but properties with nulls are not passed into the converter. I looked into using a contract resolver and value provider but it won't let me set the value of an int? to an empty string.
What seems most promising is using a custom JsonWriter that overrides the WriteNull method, but when I instantiate that in the converter's WriteJson method, it doesn't write anything out.
public class NullJsonWriter : JsonTextWriter
{
public NullJsonWriter(TextWriter writer) : base(writer)
{
}
public override void WriteNull()
{
base.WriteValue(string.Empty);
}
}
public class GamesJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IEnumerable<IGame>).IsAssignableFrom(objectType);
}
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 = new NullJsonWriter(new StringWriter(new StringBuilder()));
writer.WriteStartObject();
if (typeof (IEnumerable).IsAssignableFrom(value.GetType()))
{
var list = (IEnumerable<IGame>)value;
if (!list.Any())
{
writer.WriteEndObject();
return;
}
var properties = list.First().GetType().GetProperties();
PropertyInfo gameId = null;
foreach (var prop in properties)
{
if (prop.Name == "GameId")
{
gameId = prop;
break;
}
}
for (int i = 0; i < list.Count(); i++)
{
writer.WritePropertyName(string.Format("{0}", gameId.GetValue(list.ElementAt(i))));
serializer.Serialize(writer, list.ElementAt(i));
}
}
writer.WriteEndObject();
}
I can see JSON text in the StringBuilder of the writer but it doesn't actually get written out to my webpage.
I'm not sure I understand the reasoning of why you want to write out an empty string in place of all nulls, even those that might represent a Nullable<int>, or other object. The problem with that strategy is it makes the JSON more difficult to deal with during deserialization: if the consumer is expecting a Nullable<int> and they get a string instead, it leads to errors, confusion, and essentially forces the consuming code to use a special converter or weakly typed objects (e.g. JObject, dynamic) to get around the mismatch. But let's put that aside for now and say that you have your reasons.
You can definitely do what you want by subclassing the JsonTextWriter class and overriding the WriteNull method to write an empty string instead. The trick is that you must instantiate your own JsonSerializer instead of using JsonConvert.SerializeObject, since the latter does not have any overloads that accept a JsonWriter. Here is a short proof-of-concept that shows that this idea will work:
class Program
{
static void Main(string[] args)
{
// Set up a dummy object with some data. The object also has a bunch
// of properties that are never set, so they have null values by default.
Foo foo = new Foo
{
String = "test",
Int = 123,
Decimal = 3.14M,
Object = new Bar { Name = "obj1" },
Array = new Bar[]
{
new Bar { Name = "obj2" }
}
};
// The serializer writes to the custom NullJsonWriter, which
// writes to the StringWriter, which writes to the StringBuilder.
// We could also use a StreamWriter in place of the StringWriter
// if we wanted to write to a file or web response or some other stream.
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
using (NullJsonWriter njw = new NullJsonWriter(sw))
{
JsonSerializer ser = new JsonSerializer();
ser.Formatting = Formatting.Indented;
ser.Serialize(njw, foo);
}
// Get the JSON result from the StringBuilder and write it to the console.
Console.WriteLine(sb.ToString());
}
}
class Foo
{
public string String { get; set; }
public string NullString { get; set; }
public int? Int { get; set; }
public int? NullInt { get; set; }
public decimal? Decimal { get; set; }
public decimal? NullDecimal { get; set; }
public Bar Object { get; set; }
public Bar NullObject { get; set; }
public Bar[] Array { get; set; }
public Bar[] NullArray { get; set; }
}
class Bar
{
public string Name { get; set; }
}
public class NullJsonWriter : JsonTextWriter
{
public NullJsonWriter(TextWriter writer) : base(writer)
{
}
public override void WriteNull()
{
base.WriteValue(string.Empty);
}
}
Here is the output:
{
"String": "test",
"NullString": "",
"Int": 123,
"NullInt": "",
"Decimal": 3.14,
"NullDecimal": "",
"Object": {
"Name": "obj1"
},
"NullObject": "",
"Array": [
{
"Name": "obj2"
}
],
"NullArray": ""
}
Fiddle: https://dotnetfiddle.net/QpfG8D
Now let's talk about why this approach did not work as expected inside your converter. When JSON.Net calls into your converter, it already has a JsonWriter instantiated, which gets passed into the writer parameter of the WriteJson method. Instead of writing to that instance, what you have done is overwritten this variable with a new NullJsonWriter instance. But remember, replacing the contents of a reference variable does not replace the original instance that variable refers to. You are successfully writing to your new writer instance in the converter, but all the output that you are writing is going into the StringBuilder which you instantiated at the beginning of the WriteJson method. Since you never take the JSON result from the StringBuilder and do something with it, it is simply thrown away at the end of the method. Meanwhile, Json.Net continues to use its original JsonWriter instance, to which you have not written anything inside your converter. So, that is why you are not seeing your customized JSON in the final output.
There are a couple of ways to fix your code. One approach is just to use a new variable when instantiating the NullJsonWriter in your converter instead of hijacking the writer variable. Then, at the end of the WriteJson method, get the JSON from the StringBuilder and write it to the original writer using WriteRawValue method on the original writer.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
StringBuilder sb = new StringBuilder();
using (var innerWriter = new NullJsonWriter(new StringWriter(sb))
{
innerWriter.WriteStartObject();
// ... (snip) ...
innerWriter.WriteEndObject();
}
writer.WriteRawValue(sb.ToString());
}
Another approach, depending on your intended result, is not to use a separate writer inside your converter at all, but instead to create the NullJsonWriter instance at the very beginning of serialization and pass it to the outer serializer (as I did in the proof-of-concept earlier in the post). When Json.Net calls into your converter, the writer that is passed to WriteJson will be your custom NullJsonWriter, so at that point you can write to it naturally without having to jump through hoops. Keep in mind, however, that with this approach your entire JSON will have the nulls replaced with empty strings, not just the IEnumerable<IGame> that your converter handles. If you are trying to confine the special behavior to just your converter, then this approach will not work for you. It really depends on what you are trying to accomplish-- do you want the nulls replaced everywhere or just in part of the JSON? I did not get a good sense of this from your question.
Anyway, hope this helps.
JsonSerializer _jsonWriter = new JsonSerializer
{
NullValueHandling = NullValueHandling.Include
};
Use this to make the serializer not ignore null values, then you should be able to override the serialization with the code you already have :)