When serializing an object, I have a List<T> property where T is an abstract type which I'd like to serialize naturally. However, when deserializing the object, I want/need to manually deserialize this abstract list. I'm doing the latter part with a custom JsonConverter which looks at a property on each item
in the list and then deserializes it to the correct type and puts it in the list.
But I also need to deserialize the rest of the object, without doing it property-by-property. Pseudo-code follows.
MyObject
class MyObject {
public Guid Id;
public string Name;
public List<MyAbstractType> Things;
}
MyObjectConverter
class MyObjectConverter : JsonConverter {
public override object ReadJson(reader, ..., serializer) {
// Line below will fail because the serializer will attempt to deserlalize the list property.
var instance = serializer.Deserialize(reader);
var rawJson = JObject.Load(reader); // Ignore reading from an already-read reader.
// Now loop-over and deserialize each thing in the list/array.
foreach (var item in rawJson.Value<JArray>(nameof(MyObject.Things))) {
var type = item.Value<string>(nameof(MyAbstractType.Type));
MyAbstractType thing;
// Create the proper type.
if (type == ThingTypes.A) {
thing = item.ToObject(typeof(ConcreteTypeA));
}
else if (type == ThingTypes.B) {
thing = item.ToObject(typeof(ConcreteTypeB));
}
// Add the thing.
instance.Things.Add(thing);
}
return instance;
}
}
To recap, I want to manually handle the deserialization of Things, but allow them to naturally serialize.
Related
I use Manatee.Json.JsonValue a whole lot, for instance in a case I have now, where I have a property of type object. This object property can contain both literals or complex types.
When the property value contains a complex type of JsonValue, it is serialized as an empty object! Is it possible to avoid this, such that other property values are serialized, while object property values of type JsonValue are just copied?
Here is an example with Manatee.Json.13.0.2:
public class TestObject
{
public object Property { get; set; }
}
[TestClass]
public class TestClass
{
[TestMethod]
public void Test()
{
var testObject = new TestObject
{
Property = new JsonValue("testString")
};
JsonValue expected = new JsonValue(
new JsonObject
{
["Property"] = "testString",
}
);
var serializer = new Manatee.Json.Serialization.JsonSerializer();
JsonValue actual = serializer.Serialize(testObject);
var expectedString = expected.ToString();
/// expectedString "{\"Property\":\"testString\"}" string
var actualString = actual.ToString();
/// actualString "{\"Property\":{}}" string
Assert.AreNotEqual(expectedString, actualString);
}
}
Anyone have any idea how to do this?
So, I've come to find a solution that seems to cover my use case.
I have managed to create a simple JsonSerializer that copies values. Upon serialization, the goal is to go from the source object to a JsonValue target. If the source value is either a JsonValue, a JsonObject or a JsonArray instance, the serialization method returns a JsonValue. Other types throws an ArgumentOutOfRangeException.
Upon deserialization, the goal is to go from a JsonValue and back to the requested type. If the requested type is a JsonValue, the current JsonValue are returned. If the requested type is a JsonObject, either the current JsonValue.Object are returned, or a new JsonObject if JsonValue.Object is null. Same goes for JsonArray: JsonValue.Array ?? new JsonArray. Other requested types returns null;
The serializer must be registered with the SerializerFactory.AddSerializer(mySerializer) somewhere. For me it is typically in Global.asax.cs and in a MSTest [AssemblyInitialize()] method.
Here is my implementation at this point:
public class JsonValueSerializer : ISerializer
{
public bool ShouldMaintainReferences => false;
private Type[] HandledTypes = {
typeof(JsonValue),
typeof(JsonObject),
typeof(JsonArray)
};
public bool Handles(SerializationContextBase context)
{
return HandledTypes.Contains(context.InferredType);
}
public JsonValue Serialize(SerializationContext context)
{
if (context.Source is JsonValue jsonValue)
return jsonValue;
else if (context.Source is JsonObject jsonObject)
return new JsonValue(jsonObject);
else if (context.Source is JsonArray jsonArray)
return new JsonValue(jsonArray);
else
throw new ArgumentOutOfRangeException();
}
public object Deserialize(DeserializationContext context)
{
if (context.RequestedType == typeof(JsonValue))
{
return context.LocalValue;
}
else if (context.RequestedType == typeof(JsonObject))
{
return context.LocalValue.Object ?? new JsonObject();
}
else if (context.RequestedType == typeof(JsonArray))
{
return context.LocalValue.Array ?? new JsonArray();
}
return null!;
}
}
Weakness of this implementation:
Deserialization can be done with serializer.Deserialize<JsonObject>(myJsonValue). A problem with this implementation, is that when I have a complex class with an object property, and that property contains a JsonObject (or a JsonArray), it works to serialize the complex type into JsonValue, but upon deserialization, the previous JsonObject would be deserialized into a JsonValue of JsonValueType.Object. This is different from the original JsonObject, since JsonValue can contain a JsonObject, but JsonObject is a type in its own right.
Without additional mechanisms, it would be impossible to know if the JsonValue should be kept or if it should be turned back into a JsonObject or a JsonArray. One could decide that on deserialization, all JsonValue.Object would be constantly set to a JsonObject type, but that would be wrong when it initially was a JsonValue of JsonValueType.Object (same for JsonArray). Another approach could maybe be to let JsonObject get a "$type" parameter with the information, but that wouldn't work with JsonArray. I have no fix for this behaviour.
Appologies if its already been asked, I could not find anything helpful to my situation.
I need to deserialize a JSON in a property of my object instead of a whole object. The reason I am trying to do it, is that is simply generics.
I have the following situation
For instance I have
Class User
{
int UserId {get;set;}
string Name {get;set;
}
Class Wanted : CustomClass
{
User[] Users {get;set;}
public override void Map(){ }
public override void Scan(){ }
}
My Json is:
[
{
"userId": 1,
"name": "Josh"
},
{
"userId": 5,
"name" : "Martin"
}
]
Is it possible to deserialize(+ generics) my JSON directly into my Wanted class instead of serializing into A and then assign it into Wanted ?
The goal is after the serialization I will have object with type Wanted and an array with 2 users in it.
Since the JSON does not match the class you want to deserialize into, and you cannot change the JSON, you will need to use a custom JsonConverter to bridge the gap.
To make it work you'll need to introduce an interface IHasUsers which your Wanted class (or its base class) will need to implement:
interface IHasUsers
{
User[] Users { get; set; }
}
class Wanted : CustomClass, IHasUsers
{
public User[] Users { get; set; }
...
}
Then you can make a generic converter which will instantiate the Wanted class (or any other class which implements IHasUsers) and populate the Users property:
class UserListConverter<T> : JsonConverter where T: IHasUsers, new()
{
public override bool CanConvert(Type objectType)
{
return typeof(IHasUsers).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray array = JArray.Load(reader);
T obj = new T() { Users = array.ToObject<User[]>() };
return obj;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then you can deserialize your JSON like this:
Wanted wanted = JsonConvert.DeserializeObject<Wanted>(json, new UserListConverter<Wanted>());
Here is a demo: https://dotnetfiddle.net/KL6Ok6
Hope this is what you were looking for.
Since Wanted is "your desired class", there needs to be an instance of Wanted created somewhere. You might just as well create it yourself rather than having a derserializer do it for you. Once you have done this you can simply set the Users property to the deserialized data:
var wanted = new Wanted() { Users = JsonConvert.DeSerialize<User[]>(myString) };
You don't deserialize some data "into a property" without deserializing it to some object of some type first. Once you have done this you can then set the property to the object that contains the deserialized data.
There is nothing generic about Wanted here though and the deserializer cannot be supposed to figure out that it should create a Wanted or any other type unless you specify the type to derserialize the data to somewhere.
And there is no point of deserializing the data to a type defined at compile time if you don't know that the data matches this type. Then you might as well create an anonymous object or a dictionary of key/value pairs.
You can use Newtonsoft.json . Try below
var files = JArray.Parse(YourJSON);
var recList = files.SelectTokens("$").ToList();
foreach (JObject item in recList.Children())
{
foreach (JProperty prop in item.Children())
{
string key = prop.Name.ToString();
string value = prop.Value.ToString();
// and add these to an array
}
}
I am having trouble trying to determine how to make my Serialization Properly be able to access a single result, as well as an array.
When I make a REST call looking for something on a server, sometimes it will return an Array of models, but if the search results only have a single model, it will not be returned as an error. This is when I get an exception that I cannot deserialize because the Object Property is expecting an array, but is instead receiving a single object.
Is there a way to define my class so that it can handle a single object of type ns1.models when that is returned instead of an array of objects?
[JsonObject]
public class Responses
{
[JsonProperty(PropertyName = "ns1.model")]
public List<Model> Model { get; set; }
}
Response that can be deserialized:
{"ns1.model":[
{"#mh":"0x20e800","ns1.attribute":{"#id":"0x1006e","$":"servername"}},
{"#mh":"0x21a400","ns1.attribute":{"#id":"0x1006e","$":"servername2"}}
]}
Response that cannot be serialized (because JSON includes only a singe "ns1.model"):
{"ns1.model":
{"#mh":"0x20e800","ns1.attribute":{"#id":"0x1006e","$":"servername"}}
}
Exception:
Newtonsoft.Json.JsonSerializationException was unhandled HResult=-2146233088 Message=Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[ConsoleApplication1.Model]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object. Path '['ns1.model-response-list'].['ns1.model-responses'].['ns1.model'].#mh', line 1, position 130
To handle this you have to use a custom JsonConverter. But you probably already had that in mind.
You are just looking for a converter that you can use immediately. And this offers more than just a solution for the situation described.
I give an example with the question asked.
How to use my converter:
Place a JsonConverter Attribute above the property. JsonConverter(typeof(SafeCollectionConverter))
public class Response
{
[JsonProperty("ns1.model")]
[JsonConverter(typeof(SafeCollectionConverter))]
public List<Model> Model { get; set; }
}
public class Model
{
[JsonProperty("#mh")]
public string Mh { get; set; }
[JsonProperty("ns1.attribute")]
public ModelAttribute Attribute { get; set; }
}
public class ModelAttribute
{
[JsonProperty("#id")]
public string Id { get; set; }
[JsonProperty("$")]
public string Value { get; set; }
}
And this is my converter:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
namespace stackoverflow.question18994685
{
public class SafeCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//This not works for Populate (on existingValue)
return serializer.Deserialize<JToken>(reader).ToObjectCollectionSafe(objectType, serializer);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
And this converter uses the following class:
using System;
namespace Newtonsoft.Json.Linq
{
public static class SafeJsonConvertExtensions
{
public static object ToObjectCollectionSafe(this JToken jToken, Type objectType)
{
return ToObjectCollectionSafe(jToken, objectType, JsonSerializer.CreateDefault());
}
public static object ToObjectCollectionSafe(this JToken jToken, Type objectType, JsonSerializer jsonSerializer)
{
var expectArray = typeof(System.Collections.IEnumerable).IsAssignableFrom(objectType);
if (jToken is JArray jArray)
{
if (!expectArray)
{
//to object via singel
if (jArray.Count == 0)
return JValue.CreateNull().ToObject(objectType, jsonSerializer);
if (jArray.Count == 1)
return jArray.First.ToObject(objectType, jsonSerializer);
}
}
else if (expectArray)
{
//to object via JArray
return new JArray(jToken).ToObject(objectType, jsonSerializer);
}
return jToken.ToObject(objectType, jsonSerializer);
}
public static T ToObjectCollectionSafe<T>(this JToken jToken)
{
return (T)ToObjectCollectionSafe(jToken, typeof(T));
}
public static T ToObjectCollectionSafe<T>(this JToken jToken, JsonSerializer jsonSerializer)
{
return (T)ToObjectCollectionSafe(jToken, typeof(T), jsonSerializer);
}
}
}
What does it do exactly?
If you place the converter attribute the converter will be used for this property. You can use it on a normal object if you expect a json array with 1 or no result. Or you use it on an IEnumerable where you expect a json object or json array. (Know that an array -object[]- is an IEnumerable)
A disadvantage is that this converter can only be placed above a property because he thinks he can convert everything. And be warned. A string is also an IEnumerable.
And it offers more than an answer to the question:
If you search for something by id you know that you will get an array back with one or no result.
The ToObjectCollectionSafe<TResult>() method can handle that for you.
This is usable for Single Result vs Array using JSON.net
and handle both a single item and an array for the same property
and can convert an array to a single object.
I made this for REST requests on a server with a filter that returned one result in an array but wanted to get the result back as a single object in my code. And also for a OData result response with expanded result with one item in an array.
Have fun with it.
I think your question has been answered already. Please have a look at this thread:
How to handle both a single item and an array for the same property using JSON.net .
Basically the way to do it is to define a custom JsonConvertor for your property.
There is not an elegant solution to your problem in the current version of JSON.NET. You will have to write custom parsing code to handle that.
As #boyomarinov said you can develop a custom converter, but since your JSON is pretty simple you can just parse your JSON into an object and then handle the two cases like this:
var obj = JObject.Parse(json);
var responses = new Responses { Model = new List<Model>() };
foreach (var child in obj.Values())
{
if (child is JArray)
{
responses.Model = child.ToObject<List<Model>>();
break;
}
else
responses.Model.Add(child.ToObject<Model>());
}
Use JRaw type proxy property ModelRaw:
public class Responses
{
[JsonIgnore]
public List<Model> Model { get; set; }
[JsonProperty(PropertyName = "ns1.model")]
public JRaw ModelRaw
{
get { return new JRaw(JsonConvert.SerializeObject(Model)); }
set
{
var raw = value.ToString(Formatting.None);
Model = raw.StartsWith("[")
? JsonConvert.DeserializeObject<List<Model>>(raw)
: new List<Model> { JsonConvert.DeserializeObject<Model>(raw) };
}
}
}
I want to serialize a MyClass, which is a class that contains a list MyClass.
In the XML, I want to write only myClass.Name, then when I deserialize it, I then find which MyClass should be in which other MyClass. I have the following code that properly serializes the list of MyClass into a list of string. However, it doesn't deserialize the list of string.
//List of actual object. It's what I use when I work with the object.
[XmlIgnore]
public List<TaskConfiguration> ChildTasks { get; set; }
//Used by the serializer to get the string list, and used
//by the serializer to deserialize the string list to.
[XmlArray("ChildTasks")]
public List<string> ChildTasksSurrogate
{
get
{
List<string> childTaskList = new List<string>();
if (ChildTasks != null)
childTaskList.AddRange(ChildTasks.Select(ct => ct.Name).ToList());
if (_childTasksSurrogate != null)
childTaskList.AddRange(_childTasksSurrogate);
//Clears it not to use it when it serializes.
_childTasksSurrogate = null;
return childTaskList;
}
set
{
_childTasksSurrogate = value;
}
}
[XmlIgnore]
private List<string> _childTasksSurrogate;
As I said, the serialization works. The problem lies with the deserialization. After the deserialization, MyClass._childTasksSurrogate is null.
The problem was related to HOW does the XmlSerializer deserializes the Xml :
I thought that the XmlSerializer would assign the whole property (read: myList = DeserializedList), while it looks like it adds all the elements (read: myList.AddRange(DeserializedList).
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.