Deserializing nodes presenting as different types - c#

I am looping over a folder of JSON files, and I am trying to pull out some information from them; however, I am finding it greatly difficult to do so.
I have started to build my objects to deserialize. I have a particular node I would normally deserialize as an object, but when it's empty, it presents as an empty array. Please see the definition field in the example JSON below:
{
"name": "Example",
"description": "Example JSON",
"properties": {
"foo": "bar",
"foo1": "bar2",
"foo3": "bar4"
},
"stages": {
"This is a stage": {
"stageInfo1": "blah",
"stageInfo2": "blah",
"integration": {
"x": "x",
"y": "y",
"z": "z",
"definition": []
}
},
"Another Stage": {
"stageInfo1": "blah",
"stageInfo2": "blah",
"integration": {
"x": "x",
"y": "y",
"z": "z",
"definition": {
"5a4d7de4c6518": {
"Editable": true,
"ID": "5a4d7de4c6518",
"Name": "My example"
}
}
}
}
}
}
As the definition name can change (in this case it is 5a4d7de4c6518), I thought a dictionary would be best, but an error is thrown when an empty array is presented.
[JsonProperty("definition")]
public Dictionary<string, Definition> definition;
Error:
An unhandled exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
Additional information: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'System.Collections.Generic.Dictionary`2[System.String,JsonProcessReader.Models.Stages+IntegrationDefinition]' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.

An empty array is incompatible with a dictionary structure, which gives rise to the error you are seeing. Since it seems you cannot easily change the JSON, you will need to use a JsonConverter to handle this situation. Here is a generic one that should work for you:
class TolerantObjectConverter<T> : JsonConverter where T: new()
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
object result = new T();
if (token.Type == JTokenType.Object)
{
serializer.Populate(token.CreateReader(), result);
}
return result;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This converter works by temporarily loading the relevant piece of JSON into a JToken and then checking whether it is actually an object before trying to convert it. So if it is an array or some other token type that won't convert properly, it will return an empty T instance instead.
To use the converter, simply add a [JsonConverter] attribute to your dictionary property like this:
public class Integration
{
...
[JsonConverter(typeof(TolerantObjectConverter<Dictionary<string, Definition>>))]
public Dictionary<string, Definition> definition { get; set; }
}
Here is a working demo: https://dotnetfiddle.net/83dQoC

Related

JsonConverter to handle dynamic key names [duplicate]

This question already has answers here:
How can I parse a JSON string that would cause illegal C# identifiers?
(3 answers)
Closed 3 years ago.
I am working on code that processes responses from OpenLibrary. This is a rest service that returns books based on a passed in ISBN. An example URL is this:
https://openlibrary.org/api/books?bibkeys=ISBN:9780596005405&jscmd=data&format=json
{
ISBN:9780596005405: {
publishers: [
{
name: "O'Reilly"
}
],
pagination: "xxxii, 854 p. :",
identifiers: {
lccn: [
"2006281089"
],
openlibrary: [
"OL17924716M"
],
isbn_10: [
"0596005407"
],
goodreads: [
"58129"
],
librarything: [
"187028"
],
},
.... other properties omitted for brevity
}
I have these objects:
public class OLResult
{
Publishers Publishers { get; set; }
// other properties
}
public class Publishers
{
// properties go here
}
I created C# objects that are identical to what OpenLibrary returns. However, if you look at the response, you'll notice it's a JSON with a root element with a weird key: ISBN: followed by the passed in ISBN number. Newtonsoft.Json does not know how to map this dynamic key to my OLResult object.
I have created a simple converter to manually convert this JSON to the correct object:
public class OpenLibraryResultConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
OLResult result = new OLResult();
// Perform magic to copy JSON results to result object.
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
I have also added an attribute [JsonConverter(typeof(OpenLibraryResultConverter))] to my OLResult class. When I call JsonConvert.DeserializeObject<OLResult>(jsonStringResult);, and use the debugger to check the ReadJson method, I can not seem to find the JSON data of the jsonStringResult parameter I passed in to the converter. Most properties of the parameters passed into the ReadJson method appear null or empty.
My question: how do I successfully read out the JSON string in my new JsonConverter?
In case you don't need that dynamic property number: ISBN:XXXXXX you can go with something simple like using partial JSON serialization that you can check out here
It's not clear from the post., but the first part is actually a label, so the overall structure is like this:
{
"ISBN:9780596005405": {...}
}
Note that the label contains a colon, which makes it more difficult to translate directly into a C# object.
But either way, you'll need to either define an object class for that and parse it, or as #Ernestas suggests, skip it.

Serialize class that provides ToString() [duplicate]

This question already has answers here:
How to make JSON.Net serializer to call ToString() when serializing a particular type?
(4 answers)
Closed 3 years ago.
Suppose I have some class that defines ToString():
class Person {
public override string ToString() { /* ... */ }
}
And suppose an instance is contained in some model:
public Person Person { get; }
Then it is serialized like this:
"person": {
"value": "Foo Bar"
}
But what I expected was this:
"person": "Foo Bar"
Can I do this somehow, or must I use a custom converter?
UPDATE
No this is not a dupe of that linked question. That shows how to do two-way conversion to/from a type. I want to do one-way conversion, given my type already has a ToString method - i.e. serialization only, not deserialization.
The question is not how to write a type converter - it is whether this one-way serialization is possible without a type converter.
I recently ran into this on a project, and the only thing my team came to, was having to write a type converter.
I want a one-way conversion which relies on my type's ToString() overload. This is for data serialized on the server and used in a REST response.
This is what I did:
public class StringConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
throw new NotSupportedException("This one-way converter cannot be used for deserialization.");
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteValue(value.ToString());
}
}
Used like this:
[JsonConverter(typeof(StringConverter))]
public Person Person { get; } // assumes Person has overloaded ToString()
And the serialized result is this:
"person": "Foo Bar"
(instead of "person": { "value": "Foo Bar" }.)
Maybe a better name is ToStringSerializer or OneWayStringConverter.

Ignore duplicates when serializing array with JSON.Net

Clarification (to anyone in the same situation):
Note that my task is to serialize an existing legacy object. As such, I would prefer to tune the serializer rather than interfere with the data structure.
I believe in most cases it's better to remove the duplicates directly from the data, as indicated by #danny-chen's answer.
As part of my object that I want to serialize with JSON.Net, there is a string[] files property which contains duplicates:
some/path/to/f1.jpg
some/path/to/f1.jpg
some/path/to/f2.jpg
some/path/to/f3.jpg
some/path/to/f2.jpg
And let's suppose these are not necessarily in order (f2, f3, f2).
Is it possible to serialize the array and ignore the duplicates ? Expected result:
{
"files": [
"some/path/to/f1.jpg",
"some/path/to/f2.jpg",
"some/path/to/f3.jpg"
]
}
I have tried the PreserveReferencesHandling setting, but it didn't work as each file in the array is a different object, with a possibly repeated value.
It's not part of the serialization, it's part of the data processing. I suggest you remove the duplicates before serialization.
string[] files = GetFiles();
data.Files = files.Distinct().ToArray();
//serialize data
//instead of data.Files = files; and do tricky things in serialization
The simplest solution is to filter the list before serialization as suggested by #Danny Chen. However, if you absolutely have to do it during serialization you can use a custom JsonConverter.
Here is the code you would need:
public class RemoveDuplicatesConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IEnumerable<T>).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartArray();
foreach (T item in ((IEnumerable<T>)value).Distinct())
{
serializer.Serialize(writer, item);
}
writer.WriteEndArray();
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, add a [JsonConverter] attribute to the list or array property in your class for which you'd like to remove duplicates, as shown below. Be sure the generic type of the converter matches the type of your list.
class Foo
{
[JsonProperty("files")]
[JsonConverter(typeof(RemoveDuplicatesConverter<string>))]
public string[] Files { get; set; }
}
Then serialize as normal. The list in the JSON will have the duplicates removed, but the original list in your object will be unaffected.
string json = JsonConvert.SerializeObject(your_object, Formatting.Indented);
Fiddle: https://dotnetfiddle.net/vs2oWQ

Correctly Override Newtonsoft.Json Method

Background
I need to override the below method so it will deserialize an object's property without failing.
JsonConvert.DeserializeObject()
It is failing because it is trying to convert a key pair value which contains either "Y" or "N" to a property which is of type Boolean.
This is the error
Could not convert string to boolean: Y.
I'm calling the method like this:
private List<T> GetBindingSource<T>(List<T> list, string JsonKey, Dictionary<string, string> dictOriginalJSON)
{
var OutJson = "";
if (dictOriginalJSON.TryGetValue(JsonKey, out OutJson))
{
list = JsonConvert.DeserializeObject<List<T>>(OutJson); //Call fails here
}
return list;
}
My Attempted Solution
After reading up on the issue it seems the best thing to do would be to override the method. I've chosen this solution by #entre
How to get newtonsoft to deserialize yes and no to boolean
using System;
using Newtonsoft.Json;
namespace JsonConverters
{
public class BooleanJsonConverter : JsonConverter
{
public override bool CanConvert( Type objectType )
{
return objectType == typeof( bool );
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
switch ( reader.Value.ToString().ToLower().Trim() )
{
case "true":
case "yes":
case "y":
case "1":
return true;
case "false":
case "no":
case "n":
case "0":
return false;
}
// If we reach here, we're pretty much going to throw an error so let's let Json.NET throw it's pretty-fied error message.
return new JsonSerializer().Deserialize( reader, objectType );
}
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
}
Problem
When I make the new call to the extended class like below.
list = BooleanJsonConverter.DeserializeObject<List<T>>(OutJson);
I get this error message.
QueueDetail.BooleanJsonConverter' does not contain a definition for
'DeserializeObject'
Question
What am I doing wrong? This is the first time I've attempted this, so I may well be missing something.
If BooleanJsonConverter inherits JsonConverter. Why doesn't BooleanJsonConverter contain the call i was using previously ?
You haven't told JsonSerializer to use your converter.
See documentation of JSON.NET here:
http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
then You can try call like this:
JsonConvert.DeserializeObject<<List<T>>(OutJson, new BooleanJsonConverter(typeof(<List<T>)));
You can also use Json atributte in Your T object.
[JsonConverter(typeof(BooleanJsonConverter))]

Deserializing JSON using Newtonsoft in C#

I have the following JSON:
[
{
"name": "codeURL",
"value": "abcd"
},
{
"name": "authURL",
"value": "fghi"
}
]
I created the following objects:
public class ConfigUrlModel {
[JsonProperty("name")]
public abstract string name { get; set; }
[JsonProperty("value")]
public abstract string value { get; set; }
}
public class ConfigUrlsModel {
[JsonProperty]
public List<ConfigUrlModel> ConfigUrls { get; set; }
}
I am deserializing with the following line:
resultObject = Newtonsoft.Json.JsonConvert.DeserializeObject<ConfigUrlsModel>(resultString);
ConfigUrlsModel result = resultObject as ConfigUrlsModel;
I am getting the following error:
Exception JsonSerializationException with no inner exception: Cannot deserialize JSON array into type 'Microsoft.Xbox.Samples.Video.Services.Jtv.Models.ConfigUrl.ConfigUrlsModel'.
Exception JsonSerializationException with no inner exception: Cannot deserialize JSON array into type 'Microsoft.Xbox.Samples.Video.Services.Jtv.Models.ConfigUrl.ConfigUrlsModel'.
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureArrayContract(Type objectType, JsonContract contract)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contr at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureArrayContract(Type objectType, JsonContract contract)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contrNavigationService:OnNavigateToMessage PageSourceUri=/Microsoft.Xbox.Sample.UI;component/ErrorPrompt/ErrorPromptView.xaml
What am I doing wrong? How do I fix this?
The root JSON container is an array, not an object, so deserialize it thusly:
var configUrls = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ConfigUrlModel>>(resultString);
var result = new ConfigUrlsModel { ConfigUrls = configUrls }; // If you still need the root object.
A JSON array is an ordered list of values [value1, value2, ..., value], which is what is shown in your question. Json.NET will convert .NET arrays and collections to JSON arrays, so you need to deserialize to a collection type.
The JSON you are sending is an array, but you are attempting to deserialize it to an object. Ether change your JSON so it matches the object defintion at the top level, and has a matching attribute, like this:
{
"ConfigUrls":[
{
"name":"codeURL",
"value":"abcd"
},
{
"name":"authURL",
"value":"fghi"
}
]
}
Or change your deserialize call to:
var urls = DeserializeObject<List<ConfigUrlModel>>(json);
This will return a List<ConfigUrlModel> which you can either use directly, or wrap in a ConfigUrlModels instance if you need to.
Additionally it is possible to deserialize this JSON directly to the desired class, by creating a custom newtonsoft JsonConverter sublcass. But that's going to make the code a little less clear, so avoid it if possible.

Categories

Resources