Deserialize deep-nested arrays - c#

Currently, I have this json structure:
{
key1: value1,
key2:
[
{key3: value3}
{key4: value4}
]
}
and deserialize it into the types
IDictionary<string, object>
IDictionary<string, IList<Dictionary<string, object>>>
using a CustomCreationConverter:
public class NestedArrayConverter : CustomCreationConverter<IList<Dictionary<string, object>>>
{
public override IList<Dictionary<string, object>> Create(Type objectType)
{
return new List<Dictionary<string, object>>();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
return base.ReadJson(reader, objectType, existingValue, serializer);
return serializer.Deserialize(reader);
}
}
Now I need to go one level deeper, e.g. plugging another array into value3. This is arbitrary, so value4 can be something different. How could this be done?
In the end I write the data to MongoDB and the driver doesn't work if the type is JArray or JObject. However I need structured data, a string is not enough.

It seems there's no built-in way of de-/serializing deep-nested json into anything other than JObject/JArray. So I use strings instead. I suppose one could write a converter to handle everything, however not needed in my case.
public class NestedArrayConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value.GetType() == typeof(List<string>))
{
var list = (List<string>)value;
writer.WriteStartArray();
foreach (var str in list)
writer.WriteRawValue(str);
writer.WriteEndArray();
}
else
{
writer.WriteValue(value);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
reader.Read();
var value = new List<string>();
while (reader.TokenType != JsonToken.EndArray)
{
value.Add(JObject.Load(reader).ToString());
reader.Read();
}
return value;
}
return serializer.Deserialize(reader);
}
}

Related

Why is my JSON serializer giving a 500 error when changing property from byte[] to List<byte[]>?

I was uploading images using a pure JSON input and Base64 encoding the images.
The code worked fine when I used property of type byte[]
But when I want to support upload of multiple files in one go and changed the property to List<byte[]> then I get a 500 error as follows
System.Private.CoreLib: Unable to cast object of type 'System.Byte[]' to type 'System.Collections.Generic.List`1[System.Byte[]]'
I am assuming the problem is in my serializer method which looks as follows
public class Base64FileJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader?.Value == null) return Array.Empty<byte>();
return Convert.FromBase64String(reader.Value as string);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new InvalidOperationException("Read only converter");
}
}
}
The serializer is applied on the input model as follows
[ApiBodyParameter(
IsOptional = true,
Description = "List/array of BASE64 encoded image data for the item images"
)]
[JsonConverter(typeof(Base64FileJsonConverter))]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public List<byte[]> ResellImageBase64Data { get; set; }
I just cannot figure out what is wrong here and how to fix it?
I tried iterating the array but I cant figure out the right way
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader?.Value == null) return Array.Empty<byte>();
var returnList = new List<byte[]>();
foreach (var image in reader.ReadAsBytes())
{
var jongel = Convert.FromBase64String(image.ToString());
returnList.Add(jongel);
}
return returnList;
}
You need to return List<byte[]> instead of byte[] in your converter. So you need to manually iterate over array in JsonReader.
You need something like
if (reader.TokenType == JsonToken.StartArray)
{
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndArray)
break;
list.Add((string)reader.Value);
}
}

Single Value to Collection json converter with generic initialization

Some time ago I needed a custom generic json-converter which automatically would convert object to collection if needed. My solution was:
public class SingleValueCollectionConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return reader.TokenType == JsonToken.StartArray ? serializer.Deserialize(reader, objectType) : new List<T> { (T)serializer.Deserialize(reader, typeof(T)) };
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
It worked fine until I've got the situation when I needed to use it for the property with generic type. I've got an error for using it like this:
[JsonConverter(typeof(SingleValueCollectionConverter<TData>))]
public List<TData> Data { get; set; }
It turned out that generic parameters cannot appear in attribute declarations. I found this pull-request to newtonsoftJsonConverter - https://github.com/JamesNK/Newtonsoft.Json/issues/1332 where the decision was suggested but it was not approved by the author.
So, I created non-generic converter which works universally like generic with the help of reflection:
public class SingleValueCollectionConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
foreach (var prop in objectType.GetProperties())
{
var type = prop.PropertyType;
if (!type.IsClass)
continue;
var destination = Activator.CreateInstance(objectType);
var result = reader.TokenType == JsonToken.StartArray
? serializer.Deserialize(reader, objectType)
: new List<object> { serializer.Deserialize(reader, type) };
return Mapper.Map(result, destination, result.GetType(), destination.GetType());
}
return null;
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
Using like this:
[JsonConverter(typeof(SingleValueCollectionConverter))]
public List<TData> Data { get; set; }
It works fine, but maybe somebody has better decision. I'll be appreciated for any suggestions.

How convert JSON array or JSON object to list of object?

I try to create custom JSON converter by using code for some answered question.
public class SingleValueArrayConverter<T> : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject)
{
T instance = serializer.Deserialize<T>(reader);
return new List<T>() { instance };
}
else if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<T>>(reader);
}
return null;
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This is my sample model class.
public class Foo
{
[JsonProperty("type")]
public string Type;
}
I use the following code to execute. It causes error about stack overflow.
var converter = new SingleValueArrayConverter<Foo>();
var test1 = "[{ \"type\": \"test\" }]";
var result1 = JsonConvert.DeserializeObject<List<Foo>>(test1, converter);
var test2 = "{ \"type\": \"test\" }";
var result2 = JsonConvert.DeserializeObject<List<Foo>>(test2, converter);
The easiest way to convert any JSON to .NET object is using "ToObject" method of JToken object.
public class FooConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<Foo>>();
}
var item = token.ToObject<Foo>();
return new List<Foo> { item };
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
PS. JToken object is the base class of any JSON data so we can convert JToken object to any appropriated type.
JToken - abstract base class
JContainer - abstract base class of JTokens that can contain other JTokens
JArray - represents an JSON array (contains an ordered list of JTokens)
JObject - represents an JSON object (contains a collection of JProperties)
JProperty - represents a JSON property (a name/JToken pair inside a JObject)
JValue - represents a primitive JSON value (string, number, boolean, null)

Change JSON.net content via property name?

I have this structure :
List<dynamic> lst = new List<dynamic>();
lst.Add(new{objId = 1,myOtherColumn = 5});
lst.Add(new{objId = 2,myOtherColumn = 6});
lst.Add(new{lala = "asd" ,lala2 = 7});
I'm serializing it via :
string st= JsonConvert.SerializeObject(lst);
Question:
How can I cause the serializer to change only values of the "objId" property , to something else ?
I know I should use class Myconverter : JsonConverter , but I didn't find any example which keeps the default behavior and in addition - allow me to add condition logic of serialization.
Here's a converter that will handle it, at least for simple objects as per your example. It looks for objects containing objId properties and then serialises all properties it finds on them. You may need to expand it to deal with other member types/more complex properties as required:
class MyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (var prop in value.GetType().GetProperties()) {
writer.WritePropertyName(prop.Name);
if (prop.Name == "objId") {
//modify objId values for example
writer.WriteValue(Convert.ToInt32(prop.GetValue(value, null)) + 10);
} else {
writer.WriteValue(prop.GetValue(value, null));
}
}
writer.WriteEndObject();
}
public override bool CanConvert(Type objectType)
{
//only attempt to handle types that have an objId property
return (objectType.GetProperties().Count(p => p.Name == "objId") == 1);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Alternatively, you can use a converter that specifies it will only convert int types, then query where in the JSON path you are before doing any conversions. This has the benefit of not needing to deal with all the other members of the anonymous type.
class MyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (writer.Path.EndsWith(".objId")) {
writer.WriteValue(Convert.ToInt32(value) + 10);
}
else {
writer.WriteValue(value);
}
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof (int);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Serialize root-level type differently than lower-level type

Is it possible in Json.NET to serialize a root-level type in the serialization hierarchy differently than a reference encountered at a lower level in the hierarchy?
For example, with this type
class Serialized {
public Serialized Serialized;
public int A;
}
in this setup
var serialized = new Serialized() { A = 1 };
var serialized2 = new Serialized() { A = 2 };
serialized.Serialized = serialized2;
string json = GetJson(serialized);
where json is
{
"A":1
"Serialized": {
"ref":2
}
}
Specifically, the root-level serialization should use the default serialization strategy and the lower-level ones should use a custom converter (or similar).
You need to define custom JsonConverter and decorate the Serialized property with it:
public class PropertyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
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)
{
if (value == null) return;
var serialized = (Serialized)value;
writer.WriteStartObject();
writer.WritePropertyName("ref");
writer.WriteValue(serialized.A);
writer.WriteEndObject();
}
}
And your class:
class Serialized {
[JsonConverter(typeof(PropertyConverter))
public Serialized Serialized;
public int A;
}
This will work only for one level of nesting. If you need it to work of multiple nested levels, you need to modify the converter:
public class PropertyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
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)
{
if (value == null) return;
var serialized = (Serialized)value;
writer.WriteStartObject();
writer.WritePropertyName("ref");
writer.WriteValue(serialized.A);
if(serialized.Serialized != null)
{
writer.WritePropertyName("nested");
writer.WriteValue(JsonConvert.SerializeObject(serialized.Serialized, new JsonConverter[] {new PropertyConverter()}));
}
writer.WriteEndObject();
}
}

Categories

Resources