I have an object structure like this:
public class Proposal {
public List<ProposalLine> Lines { get; set; }
public string Title { get; set; }
}
public class ProposalLine {
public Proposal Proposal { get; set; } // <- Reference to parent object
}
I try to serialize Proposal as Json, it tells me that there is a circular reference, which is correct.
Unfortunately, I can't touch the objects, since they are in a referenced DLL from another project - otherwise I'd change them.
Is there a way to serialize as Json and ignore the circular properties?
Use the Newtonsoft.Json (which is the default .net json serializer) and set
JsonSerializerSettings settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects
};
var serializer = JsonSerializer.Create(settings);
You can also globally define this variable if you are developing MVC applications...
Related
I am using NewtonSoft.Json to read/write our data as json. One (very simplified) example of this is:
{
"$type": "MyNamespace.LandingEvent, MyAssembly",
"TimeOfLanding": "2021-04-11T15:00:00.000Z",
"AirportName": "KLAX",
"AirportRunway": "25L"
}
With a C# DTO class that mimicks the properties. Note that we use TypeNameHandling.
We want to change our C# class to a more complex setup:
class Airport
{
public string Name { get; set; }
public string Runway { get; set; }
}
class LandingEvent
{
public DateTime TimeOfLanding { get; set; }
public Airport Airport { get; set; }
}
which will result in, that new data will be written to JSON as:
{
"$type": "MyNamespace.LandingEvent, MyAssembly",
"TimeOfLanding": "2021-04-11T15:00:00.000Z",
"Airport": {
"Name": "KLAX",
"Runway": "25L"
}
}
But we still need to be able to read the old JSON data and parse into the new class structure. And this is what I currently struggle with.
I know that the way to go is probably a specialized JsonConverter. I have a couple of questions in this regard:
How do I read the $type property and instantiate the right type? (my overriden CanConvert() method is fed the name of a base-class (due to the real context being more complex than this example).
I only want to do custom read, if the property AirportName exsist. How do I fall-back to default deserialization, if this is not the case?
Edit: Some clarification is in order. If I create a custom JsonConverter, then CanConvert will receive the type EventBase, but the $type can actually contain either "MyNamespace.LandingEvent, MyAssembly" or "MyNamespace.TakeoffEvent, MyAssembly". Therefore I will probably need to instantiate the returned object myself based on this value. I am not sure how, though.
You can use a custom JsonConverter to do double duty in handling both the polymorphic event types and the varying JSON formats. Below is an example. It works by loading the data into a JObject, where it can read the $type property and instantiate the correct event type. From there, it will try to populate the event object from the JSON. If the Airport fails to deserialize, it will then attempt to read the legacy airport proprties and populate a new Airport instance from that.
class EventConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(BaseEvent).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
string type = (string)obj["$type"];
BaseEvent baseEvent;
if (type.Contains(nameof(TakeoffEvent)))
{
baseEvent = new TakeoffEvent();
}
else
{
baseEvent = new LandingEvent();
}
serializer.Populate(obj.CreateReader(), baseEvent);
if (baseEvent.Airport == null)
{
baseEvent.Airport = new Airport
{
Name = (string)obj["AirportName"],
Runway = (string)obj["AirportRunway"]
};
}
return baseEvent;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Note: this assumes your class structure actually looks like this:
class Airport
{
public string Name { get; set; }
public string Runway { get; set; }
}
class BaseEvent
{
public Airport Airport { get; set; }
}
class TakeoffEvent : BaseEvent
{
public DateTime TimeOfTakeoff { get; set; }
}
class LandingEvent : BaseEvent
{
public DateTime TimeOfLanding { get; set; }
}
To use the converter, add it to the Converters collection in the JsonSerializerSettings, and pass the settings to DeserializeObject():
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
Converters = new List<JsonConverter> { new EventConverter() }
};
var baseEvent = JsonConvert.DeserializeObject<BaseEvent>(json, settings);
Here is a working demo: https://dotnetfiddle.net/jSaq4T
See also: Adding backward compatibility support for an older JSON structure
Classes change, this kind of Json strings change and will get extra features in future versions. You'll keep adjusting your declarations. With Newtonsoft, you can add custom handlers for varying class inheritance and keep using deserialize, but you'll have to maintain that code.
For dynamic Json, I find it easier to use JObject, JArray and JToken instead, to freely parse a Json string. Especially if you're only interested in some of the fields.
I can only give you an example, I think this is (a little) related to your project, but not the same part (smiley)
I use below code to decode part of a glTF 3d-object file produced by Blender in MSFS-converted format. This Json-like format consists of sections. Each Json section looks something like this,
"asset" : {
"extensions" : {
"ASOBO_normal_map_convention" : {
"tangent_space_convention" : "DirectX"
}
},
"generator" : "Extended Khronos glTF Blender I/O v1.0.0",
"version" : "2.0"
},
.. but these sections and their fields are mostly optional and in some GLtf's they are not filled in. It is not "serializable or deserializable" to classes.
I declare some
public JObject AssetObject;
.. filling it in from Json string sJson as follows:
dynamic stuff = JObject.Parse(sJson);
var pp = stuff.Children();
Dictionary<string, bool> d = new Dictionary<string, bool>();
foreach (JProperty jo in pp) d[jo.Name] = true; // all sections
string cSection= "asset";
if (!d.ContainsKey(cSection)) { LogLine(98, "Warning: BPG Json has no " + cSection + " section."); return false; }
else
{
AssetObject = (JObject)stuff[cSection];
ParseGLBAsset();
}
Notice the use of a dynamic declaration at first, a section will land in JObject via cast. I store the various parts of the section into string properties. The parse itself takes place in ParseGLBAsset(), this function looks as follows:
public void ParseGLBAsset()
{
foreach (JProperty jbp in AssetObject.Children())
if (jbp.Name == "generator")
{ GLBGenerator = jbp.Value.ToString(); }
else
if (jbp.Name == "extensions")
{
GLBAssetExtensions = jbp.Value.ToString();
LogLine(0, "Asset extensions: " + GLBAssetExtensions);
}
else
if (jbp.Name == "version")
{ GLBVersion = jbp.Value.ToString(); }
LogLine(1, "Found asset.generator=" + GLBGenerator);
LogLine(1, "Found asset.version=" + GLBVersion);
}
I would like the following Author type to have a default JsonConverter, and be able to override it at runtime.
[JsonConverter(typeof(BaseJsonConverter))]
public class Author
{
// The ID of an author entity in the application.
public int ID { set; get; }
// The ID of an Author entity in its source.
public string SourceID { set; set; }
}
I used the following code to override the default converter (i.e., BaseJsonConverter).
public class AlternativeConverter : BaseJsonConverter
{ // the serializer implementation is removed for clarity. }
// Deserialize using AlternativeConverter:
var author = JsonConvert.DeserializeObject<Author>(jsonString, new AlternativeConverter());
Question
Using the above call, the AlternativeConverter is first constructed; however, then an instance of BaseJsonConverter is initialized and used for deserialization. So, the AlternativeConverter is never used.
Executable example: https://dotnetfiddle.net/l0bgYO
Use case
The application is to convert different JSON objects, obtained from different sources, to a common C# type. Commonly data comes from a source for that we define the default converter (i.e., BaseJsonConverter), and for data coming from other sources, we define different converters per each.
Background
I am aware of methods such as this one, and indeed I am using similar method partially. With ref to that article, I need to have different _propertyMappings depending on the source of input, because in my application attribute to property mapping is not one-to-one. For instance, I have the following JSON objects:
{
"id":123
}
// and
{
"id":"456"
}
where the first JSON object should be deserialized to:
author.ID = 123
author.SourceID = null
and the second JSON object should be deserialized as:
author.ID = 0
author.SourceID = "456"
You can use a custom ContractResolver to override a [JsonConverter] attribute programmatically. To solve your problem you could make a custom resolver like this:
public class CustomResolver : DefaultContractResolver
{
private Dictionary<Type, JsonConverter> Converters { get; set; }
public CustomResolver(Dictionary<Type, JsonConverter> converters)
{
Converters = converters;
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
JsonObjectContract contract = base.CreateObjectContract(objectType);
if (Converters.TryGetValue(objectType, out JsonConverter converter))
{
contract.Converter = converter;
}
return contract;
}
}
Then, when you wanted to use the AlternativeConverter in place of the BaseJsonConverter, you could use the custom resolver like this:
// map the `Author` type to the `AlternativeConverter`
var converters = new Dictionary<Type, JsonConverter>()
{
{ typeof(Author), new AlternativeConverter() }
};
// Create a resolver with the converter mapping and add it to the serializer settings
var settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver(converters)
};
// Use the settings when deserializing
var author = JsonConvert.DeserializeObject<Author>(jsonString, settings);
Demo Fiddle: https://dotnetfiddle.net/cu0igV
Of course, if all you're really doing with these converters is remapping properties to different names, you could just use a ContractResolver for that in the first place and get rid of the converters altogether. See Json.NET deserialize or serialize json string and map properties to different property names defined at runtime for more information on that approach.
I think you should try to use different JsonSerializerSettings instances for different data sources, with different Converters collections. And remove JsonConverter attributes from your classes.
Let's say I have a Value that is deserialized from a class.
public class MyValue
{
public string MyPropertyA { get; set; }
public string MyPropertyB { get; set; }
public string DeserializationClass { get; } = typeof(MyValue).Name;
}
I serialize this using JsonConvert class. MyValue class has a property DeserializationClass that should be used as info from which class the string was serialized from. In other words, when I deserialize the string into an object, this property serves as info which class should be used to deserialize the string. However, I am kinda stuck here as I am not sure how to get back the class from the string. Can anybody help me here?
public class Program
{
void Main()
{
var serialized = Serialize();
var obj = Deserialize(serialized);
}
string Serialize()
{
var objValue = new MyValue { MyPropertyA="Something", MyPropertyB="SomethingElse" };
return JsonConvert.SerializeObject<MyClass>(value);
}
object Deserialize(string serialized)
{
//How to deserialize based on 'DeserializationClass' property in serialized string?
return = JsonConvert.Deserialize<???>(serialized);
}
}
EDIT: Modified example to make it more clear what I need as I don't have access to objValue when I need to deserialize the string.
probably you might need to use JsonSerializerSettings.
What you might need to do is
JsonSerializerSettings setting = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
};
and then while serializing use this setting.
var serialized = JsonConvert.SerializeObject(objValue,setting);
this will give you Json like this
{"$type":"WPFDatagrid.MyValue, WPFDatagrid","MyPropertyA":"Something","MyPropertyB":"SomethingElse","DeserializationClass":"MyValue"}
from this you can find the name of the class used it to actually get your type.
Hope this helps !!
There is an overload
If your Type is in form of a Namespace, you can obtain the type from a string representation:
Type objValueType = Type.GetType("Namespace.MyValue, MyAssembly");
object deserialized = JsonConvert.Deserialize(objValueType, serialized);
I have an object array that contains strings and longs and this class:
public class SimpleMailAddress
{
public string Address { get; set; }
public string Name { get; set; }
public static implicit operator MailAddress(SimpleMailAddress m)
{
return new MailAddress(m.Address, m.Name);
}
}
However, when deserializing the JSON array with Json.Net, I get an anonymous type that contains Address and Name instead of a SimpleMailAddress object.
I don't want to create a strongly-typed object to deserialize into because it will not be reused and I'd have to create a lot of objects for it. Is there a way to do this with Json.Net or any other library?
This is how I'm serializing/deserializing:
var json = JsonConvert.SerializeObject(myObject);
var myObject = JsonConvert.DeserializeObject<MailMessageRequest>(json);
And MailMessageRequest:
public class MailMessageRequest
{
public string Mailer { get; set; }
public string Method { get; set; }
public object[] Args { get; set; }
}
Json does not contain any inherit knowledge about your SimpleMailAddress class. So when you are telling it to deserialize, the fact that your Args property is of type Object, the deserializer is doing the best it can (by creating an anonymous type). It just sees data, it has no knowledge that you want a SimpleMailAddress object.
Json.net has a JObject class. Try using that instead of Object for your Args parameter if the actual contents of Args may change type.
Then, as needed, you can read the data from the JObject object.
If you don't care about the actual contents of Args, then leave it as Object and ignore it.
Edit: JSon.Net can embed type information during serialization that can be used during deserialization.
Leave your Args parameter as an Object. Then use the TypeNameHandling option of All during both serialization and deserialization.
var json = JsonConvert.SerializeObject(myObject, Formatting.None,
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
var myObject = JsonConvert.DeserializeObject<MailMessageRequest>(json,
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
You should end up with your Args object as your desired SimpleMailAddress object.
The data contact serializers built into the.net framework have the concept of known types where you tell them what types to expect and it uses those during deserialization.
There is a built in Json data contract serializer but I'm not sure it will be compatible with your Json data, it may need to be serialized and deserialized via a datacontract serializer to work using this method.
Got it. I have to use these settings:
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All,
TypeNameAssemblyFormat = FormatterAssemblyStyle.Full
};
var json = JsonConvert.SerializeObject(object, Formatting.None, settings);
var object = JsonConvert.DeserializeObject<MailMessageRequest>(message.Body, settings);
I've got a class which has been serialized into JSON, and which I'm trying to deserialize into an object.
e.g.
public class ContentItemViewModel
{
public string CssClass { get; set; }
public MyCustomClass PropertyB { get; set; }
}
the simple property (CssClass) will deserialize with:
var contentItemViewModels = ser.Deserialize<ContentItemViewModel>(contentItems);
But PropertyB gets an error...
We added a JavaScriptConverter:
ser.RegisterConverters(new List<JavaScriptConverter>{ publishedStatusResolver});
But when we added 'MyCustomClass' as a 'SupportedType', the Deserialize method was never called. However when we have ContentItemViewModel as the SupportedType, then Deserialize is called.
We've got a current solution which looks something like this:
class ContentItemViewModelConverter : JavaScriptConverter
{
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var cssClass = GetString(dictionary, "cssClass"); //I'm ommitting the GetString method in this example...
var propertyB= GetString(dictionary, "propertyB");
return new ContentItemViewModel{ CssClass = cssClass ,
PropertyB = new MyCustomClass(propertyB)}
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
throw new Exception("Only does the Deserialize");
}
public override IEnumerable<Type> SupportedTypes
{
get
{
return new List<Type>
{
typeof(ContentItemViewModel)
};
}
}
}
But we'd prefer a simpler solution of only deserializing MyCustomClass, as there are a number of other fields which are on the ViewModel, and it seems a waste to have to edit this converter every time we change/add a property....
Is there a way to Deserialize JUST PropertyB of type MyCustomClass?
Thanks for your help!
Have you considered using DatacontractJsonSerializer
[DataContract]
public class MyCustomClass
{
[DataMember]
public string foobar { get; set; }
}
[DataContract]
public class ContentItemViewModel
{
[DataMember]
public string CssClass { get; set; }
[DataMember]
public MyCustomClass PropertyB { get; set; }
}
class Program
{
static void Main(string[] args)
{
ContentItemViewModel model = new ContentItemViewModel();
model.CssClass = "StackOver";
model.PropertyB = new MyCustomClass();
model.PropertyB.foobar = "Flow";
//Create a stream to serialize the object to.
MemoryStream ms = new MemoryStream();
// Serializer the User object to the stream.
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ContentItemViewModel));
ser.WriteObject(ms, model);
byte[] json = ms.ToArray();
ms.Close();
string s= Encoding.UTF8.GetString(json, 0, json.Length);
Console.ReadLine();
}
}
Add all possible classes to DatacontractJsonSerializer.KnownTypes if MyCustomClass has derivations.
For whatever it may be worth after all this time, but I stumbled over the same problem and the solution is that the Deserializer hasn't got a clue about the classes you are deserializing unless you give him the necessary information.
On the top level, it knows the type from the type parameter of Deserialize<>(). That's why your converter for ContentItemViewModel works. For nested objects, it needs __type properties and a JavaScriptTypeResolver.
var ser = new JavaScriptSerializer(new SimpleTypeResolver());
ser.RegisterConverters(myconverters);
MyClass myObject = new MyClass();
string json = ser.Serialize(myObject);
// set a breakpoint here to see what has happened
ser.Deserialize<MyClass>(json);
A TypeResolver adds a __type property to each serialized object. You can write a custom type resolver that uses short names. In this sample, I use the SimpleTypeResolver from .net that "simply" stores the fully qualified type name as __type. When deserializing, the JavaScriptDeserializer finds __type and asks the TypeResolver for the correct type. Then it knows a type and can call a registered JavaScriptConverter.Deserialize method.
Without a TypeResolver, objects are deserialized to a Dictionary because JavaScriptSerializer doesn't have any type information.
If you can't provide a __type property in your json string, I think you'll need to deserialize to Dictionary first and then add a "guessing-step" that interprets the fields to find the right type. Then, you can use the ConvertToType method of JavaScriptSerializer to copy the dictionary into the object's fields and properties.
If you need to use the JavaScriptSerializer that is provides by ASP.NET and can't create your own, consider this section from the .ctor help of JavaScriptSerializer:
The instance of JavaScriptSerializer that is used by the asynchronous communication layer for invoking Web services from client script uses a special type resolver. This type resolver restricts the types that can be deserialized to those defined in the Web service’s method signature, or the ones that have the GenerateScriptTypeAttribute applied. You cannot modify this built-in type resolver programmatically.
Perhaps the GenerateScriptType Attribute can help you. But I don't know what kind of __type Properties are be needed here.