Why Json.NET cannot deserialize JSON arrays into object property? - c#

Temporary note: This is NOT a duplicate of the above mentioned post
Let's say I have a server-side class structure like this.
public class Test
{
// this can be any kind of "Tag"
public object Data { get; set; }
}
public class Other
{
public string Test { get; set; }
}
Now a string like this is coming from let's say the client.
{"Data": [{$type: "MyProject.Other, MyProject", "Test": "Test"}] }
When I try to deserialize this into a Test instance, I get a result where the Tag property is a JToken instead of some kind of collection, for example ArrayList or List<object>.
I understand that Json.NET cannot deserialize into a strongly typed list, but I'd expect that it respects that it's at least a list.
Here is my current deserialization code.
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
};
var str = "{\"Data\": [{\"$type\": \"MyProject.Other, MyProject\", \"Test\": \"Test\"}] }";
var test = JsonConvert.Deserialize<Test>(str, settings);
// this first assertion fails
(test.Data is IList).ShouldBeTrue();
(((IList)test.Data)[0] is Other).ShouldBeTrue();
I'm aware of the fact that if I serialize such a structure, then by default I'll get a { $type: ..., $values: [...]} structure in the JSON string instead of a pure array literal, and that will indeed properly deserialize. However, the client is sending a pure array literal, so I should be able to handle that in some way.

I managed to put together a JsonConverter to handle these kind of untyped lists. The converter applies when the target type is object. Then if the current token type is array start ([) it will force a deserialization into List<object>. In any other case it will fall back to normal deserialization.
This is a first version which passes my most important unit tests, however as I'm not a Json.NET expert, it might break some things unexpectedly. Please if anyone sees anything what I didn't, leave a comment.
public class UntypedListJsonConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanRead => true;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartArray)
{
return serializer.Deserialize(reader);
}
return serializer.Deserialize<List<object>>(reader);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(object);
}
}
Usage example:
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
Converters = new[] { new UntypedListJsonConverter() }
};
var str = "{\"Data\": [{\"$type\": \"MyProject.Other, MyProject\", \"Test\": \"Test\"}] }";
var test = JsonConvert.Deserialize<Test>(str, settings);
// now these assertions pass
(test.Data is IList).ShouldBeTrue();
(((IList)test.Data)[0] is Other).ShouldBeTrue();

Try this:
public class Test
{
public Dictionary<string, List<Other>> Data { get; } = new Dictionary<string, List<Other>>();
}
You need to set up the class you are trying to fill from json data to match as closely to the json structure. From the looks of it, the json looks a dictionary where the keys are strings and the values are arrays of Other objects.

Related

Deserialization of JSON to List of Interface with generic type parameter

As the title mentions, I am trying to deserialize a JSON but am having some trouble. I think below includes the necessary information.
public class Variable<T> : IVariable where T : IConvertible
{
//...
}
public class ArrayVariable<T> : IVariable where T : IConvertible
{
//...
}
So I have a list of IVariable which I then serialize successfully (all of the information is in the json):
JsonConvert.SerializeObject(myIVariableList)
Now I am trying to deserialize it but I am having trouble determining the correct way to go about doing it as it involves finding the generic type T in addition to the type Variable or ArrayVariable. I have already tried
JsonConvert.DeserializeObject<List<IVariable>>(result.newValues)
but obviously, you can create instances of an interface. Any help would be much appreciated.
You can use TypeNameHandling.All but I would strongly recommend you avoid it due to it being very dangerous and allows attackers to compromise your code.
Another safer option is to use a custom converter. Here's a very trivial (and fragile) example that should get you started:
First lets make some basic classes that share an interface:
public interface IVariable { }
public class Foo : IVariable
{
public int A { get; set; }
}
public class Bar : IVariable
{
public int B { get; set; }
}
Now we can make our converter:
public class IVariableConverter : JsonConverter<IVariable>
{
public override IVariable ReadJson(JsonReader reader, Type objectType,
IVariable existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// First load the JSON into a JObject
var variable = JObject.Load(reader);
// If the JSON had a property called A, it must be a Foo:
if (variable.ContainsKey("A"))
{
return variable.ToObject<Foo>();
}
// If the JSON had a property called B, it must be a Bar:
if (variable.ContainsKey("B"))
{
return variable.ToObject<Bar>();
}
// And who knows what was passed in if it was missing both of those properties?!
throw new Exception("Er, no idea what that JSON was supposed to be!");
}
public override void WriteJson(JsonWriter writer, IVariable value,
JsonSerializer serializer)
{
// Feel free to write your own code here if you need it
throw new NotImplementedException();
}
}
And now we can do some actual deserialising:
// A basic JSON example:
var json = "[{\"A\":1},{\"B\":2}]";
// The settings to tell the serialiser how to process an IVariable object
var settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new IVariableConverter() }
};
// And deserialise with the defined settings
var result = JsonConvert.DeserializeObject<List<IVariable>>(json, settings);
You will need to be a bit more creative with how you identify each type, but this is a safe way to achieve what you need.
You can use TypeNameHandling.All to add type information to your serialiazed json and then utilize it during parsing:
var variables = new List<IVariable>()
{
new Variable<int>()
};
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
var serializeObject = JsonConvert.SerializeObject(variables, settings);
var list = JsonConvert.DeserializeObject<List<IVariable>>(serializeObject, settings);

How to represent and serialize a data structure which requires duplicate keys?

I'm working with a third-party API where you send an order along with an array of options you want to add to the order. Each Option has an OptionId.
You'd assume the required JSON would look something like this:
{
//More properties cut for brevity
"options":[
{
"optionId":"ID 1"
},
{
"optionId":"ID 2"
}
]
}
Instead, the required JSON actually looks like this:
{
//More properties cut for brevity
"options":[
{
"optionId":"ID 1",
"optionId":"ID 2"
}
]
}
Is there any way I can represent that data structure in C#? And if yes, is there any way I can tell Json.NET to serialize it the way the API requires?
The desired JSON is technically valid, though not recommended because having duplicate keys in an object makes it much more difficult to work with. I understand this is a third party API, and you don't have any other choice, but I want to make clear to future readers that this is a bad design.
To represent the option IDs in C#, you can use a simple List<string>. You will need to make a custom JsonConverter to write out the desired structure to the JSON. We'll call it OptionListConverter. So declare the option list as shown below:
class Order
{
[JsonProperty("options")]
[JsonConverter(typeof(OptionListConverter))]
public List<string> OptionIds { get; set; }
}
Then define the OptionListConverter like this:
class OptionListConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<string>);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var list = (List<string>)value;
writer.WriteStartArray();
writer.WriteStartObject();
foreach (var id in list)
{
writer.WritePropertyName("optionId");
writer.WriteValue(id);
}
writer.WriteEndObject();
writer.WriteEndArray();
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
You can then create the desired JSON like this:
var order = new Order
{
OptionIds = new List<string> { "ID 1", "ID 2" }
};
var json = JsonConvert.SerializeObject(order, Formatting.Indented);
Working demo: https://dotnetfiddle.net/rEYl8p

Deserializing name / value pairs to objects

I have a collection of name / value pairs where they are defined with the words name and value just like a Key/Value object, i.e.
[{"Name":"ActivityId","DataType":1,"Value":"a7868f8c-07ac-488d-a414-714527c2e76f"},
{"Name":"Address1","DataType":2,"Value":"123 Main St"}]
If I had an object like:
class Request
{
public Guid ActivityId { get; set; }
public string Address1 {get; set; }
}
How can I deserialize this to the class above?
Should I consider a custom converter? Does Json.NET have something built-in? Is there a way to decorate the properties with an attribute that I'm missing? Would it be easier to customize the serialization?
I'm trying to avoid pulling the data for each property from a Dictionary, which would be the easy route, but would require me to do this with each custom implementation. I would prefer to do this in a base class in a single method using Json.NET (or something in the .NET framework).
I've searched quite a bit, and most examples are real name/value pairs, not prefixed with name and value, i.e.
[{"ActivityId":"a7868f8c-07ac-488d-a414-714527c2e76f"}]
Any ideas?
This can be done in a straightforward manner with a custom JsonConverter like the one below. The converter works by first transforming the array of name-value pairs into a JObject with properties mirroring the pairs, then populating the target object from the JObject using the serializer's built-in Populate method.
class NameValueConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load the array of name-value pairs and transform into a JObject.
// We are assuming all the names will be distinct.
JObject obj = new JObject(
JArray.Load(reader)
.Children<JObject>()
.Select(jo => new JProperty((string)jo["Name"], jo["Value"]))
);
// Instantiate the target object and populate it from the JObject.
object result = Activator.CreateInstance(objectType);
serializer.Populate(obj.CreateReader(), result);
return result;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// WriteJson is not called when CanWrite returns false
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
// We only want this converter to handle classes that are expressly
// marked with a [JsonConverter] attribute, so return false here.
// (CanConvert is not called when [JsonConverter] attribute is used.)
return false;
}
}
To use the converter, just add a [JsonConverter] attribute to the target class:
[JsonConverter(typeof(NameValueConverter))]
class Request
{
public Guid ActivityId { get; set; }
public string Address1 {get; set; }
}
Then, you can deserialize as you normally would:
Request req = JsonConvert.DeserializeObject<Request>(json);
Fiddle: https://dotnetfiddle.net/tAp1Py

Declared properties won't be serialized if its declaring type is a dynamic object

Let's say I've the following dynamic object:
public class SomeDynamicObject : DynamicObject
{
public string Text { get; set; }
}
If I serialize it using JsonConvert.SerializeObject(new SomeDynamicObject { Text = "hello world" }) it'll return {} instead of { "Text": "hello world" }.
I suspect the issue is that JSON.NET thinks it's a full dynamic object while my case is a dynamic object with declared members.
Is there any serialization settings or built-in converter that could be configured so JSON.NET can serialize both kinds of members?
To avoid confusion
Actual use case: I don't know which will be the types being serialized but I need to cover the whole use case of serializing declared properties of a dynamic object.
That is, I can't use attributes. That's why I'm asking if there's some converter or a serialization setting that can generalize this use case.
Update for non-attribute converter
Since you can't decorate, you lose a lot of power. Once the JsonWriter has converted to a JObject, the dynamic properties appear to be lost.
However, you can always use a little reflection in a custom converter's WriteJson method to serialize non-dynamic types.
public class SomeDynamicObject : DynamicObject
{
public string Text { get; set; }
public DynamicObject DynamicProperty { get; set; }
}
public class CustomDynamicConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
JObject jObject = JObject.Load(reader);
var target = Activator.CreateInstance(objectType);
//Create a new reader for this jObject, and set all properties to match the original reader.
JsonReader jObjectReader = jObject.CreateReader();
jObjectReader.Culture = reader.Culture;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
// Populate the object properties
serializer.Populate(jObjectReader, target);
return target;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var properties = value.GetType().GetProperties().Where(x => x.PropertyType != typeof(DynamicObject)).ToList();
JObject o = (JObject)JToken.FromObject(value);
properties.ForEach(x =>
{
o.AddFirst(new JProperty(x.Name, x.GetValue(value)));
});
o.WriteTo(writer);
}
}
If you explicitly decorate your properties with [JsonProperty], the serializer will pick them up, even if the containing type is dynamic.
public class SomeDynamicObject : DynamicObject
{
[JsonProperty]
public string Text { get; set; }
}
when serialized correctly outputs:
{"Text":"hello world"}

Serializing type information using a different JsonProperty in Json.Net

I have created several classes that map to Schema.org objects;
public class Thing {
public virtual string FullSchemaType { get { return "[schema.org]Thing"; } }
}
public class CreativeWork : Thing {
public override string FullSchemaType {get {return string.Join(":", base.FullSchemaType, "CreativeWork"); } }
[JsonProperty("author")]
public string Author { get;set; }
// etc
}
public class MediaObject : CreativeWork {
public override string FullSchemaType {get {return string.Join(":", base.FullSchemaType, "MediaObject"); } }
[JsonProperty("duration")]
public float? Duration { get;set; }
}
I have a factory class that creates e.g. a MediaObject, sets its properties. The FullSchemaType property is a Schema.org compliant way of noting its type. I am putting these objects into a database, serialising them using Json.NET 6.0.3.
I want to deserialize them into the correct C# objects. The standard Json.Net way to do this is to use TypeNameHandling - but that inserts a $type property into the serialised Json, which isn't ideal as we have several different applications interfacing with this database.
Is there a way to tell Json.NET to look at my FullSchemaType property for type binding information?
You can use JsonSerializerSettings to customise parameters of serialization: setting TypeNameHandling = TypeNameHandling.None allows you not to include $type information in serialized Json, and if you have any further problems with, say, customised converters you can use JsonSerializerSettings.IList<JsonConverter> settings to specify ones.
More detailed info is here: JsonSerializerSettings
and here: Newtonsoft.Json.TypeNameHandling
And here is a good example of JsonCreationConverter, you can overrride method ReadJson in your own converter, based on JsonConverter like this:
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
// Load JObject from stream
JObject jObject = JObject.Load(reader);
if (jObject[FullSchemaType] == {your_media_object_type_name})
MediaObject target = Create(objectType, jObject);
else if (jObject[FullSchemaType] == {your_creative_work_type_name})
CreativeWork target = Create(objectType, jObject);
else
Thing target = Create(objectType, jObject);
// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);
return target;
}

Categories