Serialize a class array members as flattened - c#

Is it possible with Json.Net to convert the following fragment to have its array members flattened ?
{
"format": "json",
"artist": "artist1",
"description": [
"description1",
"description2"
]
}
Like this :
{
"format": "json",
"artist": "artist1",
"description": "description1",
"description": "description2",
}
In short, an object would look like a dictionary but with duplicate keys allowed.
Tried to use a custom JsonConverter such as the following :
internal class MyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var strings = (string[])value;
foreach (var s in strings)
{
writer.WritePropertyName(writer.Path);
writer.WriteValue(s);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string[]);
}
}
But a JsonWriterException is raised when writing property's name :
Token PropertyName in state Property would result in an invalid JSON object. Path ''.
Creating such invalid object is fine as I have no plans on deserializing it later, actually I need that for creating an url parameters string in the form of :
format=json&artist=artist1&description=description1&description=description2
Is it worth the effort to try to use Json.Net infrastructure for achieving this ?

Here's my solution to the problem : JsonTextReader
private static string GetUrlParameters(string json)
{
JsonTextReader reader = new JsonTextReader(new StringReader(json));
string path = null;
List<string> list = new List<string>();
while (reader.Read())
{
JsonToken tokenType = reader.TokenType;
switch (tokenType)
{
case JsonToken.PropertyName:
path = reader.Path;
break;
case JsonToken.Integer:
case JsonToken.Float:
case JsonToken.String:
case JsonToken.Boolean:
string item = string.Format("{0}={1}", path, reader.Value);
list.Add(item);
break;
}
}
return string.Join("&", list);
}

Related

Is it possible to make json.net skip serialize some default value in dictionary

I would like to skip some dictionary value that same as a default value.
Here is the simplified code of that dictionary
public Dictionary<int, Item> allItems;
public class Item
{
public bool IsSelected;
public List<string> SelectionInfo;
}
So, as of right now, my JSON output looks like this:
"allItems": {
"0": {
"IsSelected": true,
"SelectionInfo": [
"yes",
"maybe",
"no"
]
},
"1": {
"IsSelected": false,
"SelectionInfo": []
}
}
I want to skip the "1" But don't just entirely skip it, at least keep the key so it can be restored at later. and have 0 and 1 on the dictionary
Like this?
"allItems": {
"0": {
"IsSelected": true,
"SelectionInfo": [
"yes",
"maybe",
"no"
]
},
"1": { }
}
I was looking around and found out that you can use JsonConverter. But my case JSON tool is on the other Project (Utility.Please.TurnToJson(item);) and I want to keep it consistent and use only one JSON across all projects. Maybe if JsonConverter is the only option, at least provide me the solution on how to pass the custom JsonConverter to that project.
///somewhat like this?
Utility.Please.TurnToJson(item, new List<object>(){
typeof(Custom1),
typeof(Custom2)
});
Here is the JsonConverter that can give you your expected output.
The converter can keep your key ai it is if IsSelected = false and SelectionInfo array is empty.
public class MyCustomJsonConverter : JsonConverter
{
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject jObj = new JObject();
if (value != null)
{
var dict = JObject.Parse(value as string)["allItems"].ToObject<Dictionary<string, Item>>();
foreach (var item in dict)
{
JObject jObject = new JObject();
if (item.Value.IsSelected == false && item.Value.SelectionInfo.Count == 0)
{
jObj.Add(new JProperty(item.Key, new JObject()));
}
else
{
jObj.Add(new JProperty(item.Key, JObject.FromObject(item.Value)));
}
}
}
JObject jMainObject = new JObject();
jMainObject.Add(new JProperty("allItems", jObj));
jMainObject.WriteTo(writer);
}
}
Usage:
string json = File.ReadAllText(#"Path to your json file");
string output = JsonConvert.SerializeObject(json, new MyCustomJsonConverter());
Output:

Cannot Deserialize the Current JSON Object (Empty Array)

I am trying to make this program that formats all these objects into a treeview, to do this (I'm using JSON for ordering the objects), I needed to parse the JSON, so I chose JSON.NET.
So here is an example of how the formatting is:
{
"Space": {
"ClassName": "SpaceObject",
"Name": "Space",
"Children": {
"Object1": {
"ClassName": "Object",
"Name": "Object1",
"Children": []
},
"Object2": {
"ClassName": "Object",
"Name": "Object2",
"Children": []
}
}
}
}
public class CObject
{
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "ClassName")]
public string ClassName { get; set; }
[JsonProperty(PropertyName = "Children")]
public IDictionary<string, CObject> Children { get; set; }
}
IDictionary<string, CObject> obj = JsonConvert.DeserializeObject<IDictionary<string, CObject>>(Json, new JsonSerializerSettings() {
MissingMemberHandling = MissingMemberHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
});
foreach (var i in obj) {
ExplorerView1.Nodes.Add(AddObject(i.Value));
}
I believe I found the error, it's due to a children array having no objects in it. I don't know how to fix this though, can anyone help?
JsonSingleOrEmptyArrayConverter<T> from this answer to Deserialize JSON when type can be different almost does what you need. It simply needs to be enhanced to allow the current contract type to be a dictionary contract as well as an object contract.
First, modify JsonSingleOrEmptyArrayConverter<T> as follows:
public class JsonSingleOrEmptyArrayConverter<T> : JsonConverter where T : class
{
//https://stackoverflow.com/questions/29449641/deserialize-json-when-type-can-be-different?rq=1
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override bool CanWrite { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var contract = serializer.ContractResolver.ResolveContract(objectType);
// Allow for dictionary contracts as well as objects contracts, since both are represented by
// an unordered set of name/value pairs that begins with { (left brace) and ends with } (right brace).
if (!(contract is Newtonsoft.Json.Serialization.JsonObjectContract
|| contract is Newtonsoft.Json.Serialization.JsonDictionaryContract))
{
throw new JsonSerializationException(string.Format("Unsupported objectType {0} at {1}.", objectType, reader.Path));
}
switch (reader.SkipComments().TokenType)
{
case JsonToken.StartArray:
{
int count = 0;
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.Comment:
break;
case JsonToken.EndArray:
// You might want to allocate an empty object here if existingValue is null
// If so, do
// return existingValue ?? contract.DefaultCreator();
return existingValue;
default:
{
count++;
if (count > 1)
throw new JsonSerializationException(string.Format("Too many objects at path {0}.", reader.Path));
existingValue = existingValue ?? contract.DefaultCreator();
serializer.Populate(reader, existingValue);
}
break;
}
}
// Should not come here.
throw new JsonSerializationException(string.Format("Unclosed array at path {0}.", reader.Path));
}
case JsonToken.Null:
return null;
case JsonToken.StartObject:
existingValue = existingValue ?? contract.DefaultCreator();
serializer.Populate(reader, existingValue);
return existingValue;
default:
throw new InvalidOperationException("Unexpected token type " + reader.TokenType.ToString());
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
}
Then deserialize as follows:
var settings = new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
Converters = { new JsonSingleOrEmptyArrayConverter<IDictionary<string, CObject>>() },
};
var obj = JsonConvert.DeserializeObject<IDictionary<string, CObject>>(Json, settings);
Notes:
My assumption is that an empty array [] is only used when there are no children. If the array is ever nonempty, this assumption will be wrong and the converter will not work correctly.
The converter returns a null value for an empty array. If you would instead prefer an empty dictionary, uncomment the following lines:
// You might want to allocate an empty object here if existingValue is null
// If so, do
// return existingValue ?? contract.DefaultCreator();
Working .Net fiddle here.

How change object type to another when deserialising with JSON.Net or call default serialiser?

When serialise, I write ClassName to object into _CurrentClassName property. And when read json with JSON.Net library I need to change object to value from this property.
{
"Field1": 0,
"Field2": "34",
"_CurrentClassName": "MyCustomClass"
}
class CustomJsonConverter : JsonConverter
{
...
public override bool CanConvert(Type objectType)
{
return objectType.IsClass;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = existingValue;
if (reader.TokenType == JsonToken.Null)
{
return null;
}
else if (reader.TokenType == JsonToken.StartObject)
{
JObject jObject = JObject.Load(reader);
JToken jToken;
if (jObject.TryGetValue("_CurrentClassName", out jToken))
{
var t = jToken.Value<string>();
Type tt = Type.GetType(objectType.Namespace + "." + t);
value = Activator.CreateInstance(tt);
return value;
}
}
return serializer.Deserialize(reader);
}
...
}
Once the object type has been inferred and the object has been instantiated, you can use JsonSerializer.Populate(jObject.CreateReader()) to populate it.
For instance:
public abstract class CustomJsonConverterBase : JsonConverter
{
protected abstract Type InferType(JToken token, Type objectType, object existingValue);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var token = JToken.Load(reader);
var actualType = InferType(token, objectType, existingValue);
if (existingValue == null || existingValue.GetType() != actualType)
{
var contract = serializer.ContractResolver.ResolveContract(actualType);
existingValue = contract.DefaultCreator();
}
using (var subReader = token.CreateReader())
{
// Using "populate" avoids infinite recursion.
serializer.Populate(subReader, existingValue);
}
return existingValue;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public class CustomJsonConverter : CustomJsonConverterBase
{
public const string ClassPropertyName = #"_CurrentClassName";
protected override Type InferType(JToken token, Type objectType, object existingValue)
{
if (token is JObject)
{
var typeName = (string)token[ClassPropertyName];
if (typeName != null)
{
var actualType = Type.GetType(objectType.Namespace + "." + typeName);
if (actualType != null)
return actualType;
}
}
return objectType;
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
CustomCreationConverter<T> also uses serializer.Populate() to fill in the just-allocated object so this is a standard way to solve this problem.
Note that you are partially replicating Json.NET's built-in TypeNameHandling functionality.
If you're not married to the _CurrentClassName property name or its value syntax you could use Json.Net's built in handling of types.
When serializing or deserializing you can pass in a JsonSerializerSettings object controlling the serialization or deserialization.
On this object you can set a TypeNameHandling property that controls how Json.Net serializes and deserializes the exact type being processed.
Here is a LINQPad example:
void Main()
{
var t = new Test { Key = 42, Value = "Meaning of life" };
var json = JsonConvert.SerializeObject(
t, Newtonsoft.Json.Formatting.Indented,
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
Console.WriteLine(json);
var obj =JsonConvert.DeserializeObject(json,
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
Console.WriteLine(obj.GetType().FullName);
}
public class Test
{
public int Key { get; set; }
public string Value { get; set; }
}
Output:
{
"$type": "UserQuery+Test, LINQPadQuery",
"Key": 42,
"Value": "Meaning of life"
}
UserQuery+Test
Here you can see that the type of the object being returned from deserialization is the Test class.

intercept JSON in C# to return a list of nullable boolean

I am getting an error when I try to return a list of nullable boolean from my JSON interceptor.
The interceptor attribute is:
[JsonConverter(typeof(NullableBoolListDeSerializer))]
public List<bool?> ExemptBenefits { get; set; }
The ReadJSON method on the interceptor is:
List<bool?> result = new List<bool?>();
(reader as Newtonsoft.Json.Linq.JTokenReader).CurrentToken.ToList().ForEach(item =>
{
string value = (String)item.ToObject(typeof(String));
switch (value.ToLower())
{
case "true":
case "yes":
case "1":
result.Add(true);
break;
case "false":
case "no":
case "0":
default:
result.Add(false);
break;
}
});
return result;
The JSON being submitted is:
{
"exemptBenefits": [
"1"
],
"_apiEndpoint": "/benefits/loan"
}
The error I am getting is:
Unexpected token when deserializing object: String. Path 'exemptBenefits[0]', line 1, position 187.
Wondering how to convert a list of strings (eg "1","0"."true", "false") from JSON to a List (true,false,true,false) in a JSON interceptor
(actually it is NewtonSoft.Json)
If you want to convert a list of string values into a list of nullable boolean values with a JsonConverter class, I would recommend using a JArray inside the converter instead of trying to deal with the reader directly. This will allow you to simplify your code while also avoiding the error you encountered:
class NullableBoolListDeSerializer : JsonConverter
{
readonly string[] TrueStrings = { "true", "yes", "1" };
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<bool?>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JArray.Load(reader)
.Children<JValue>()
.Select(jv =>
{
string b = (string)jv;
return b != null ? TrueStrings.Contains(b.ToLower()) : (bool?)null;
})
.ToList();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Fiddle: https://dotnetfiddle.net/JaP5W7
Of course, you can actually do better than that. Instead of making your converter handle a List<bool?>, make it handle just a simple bool? instead, e.g.:
class NullableBoolDeSerializer : JsonConverter
{
readonly string[] TrueStrings = { "true", "yes", "1" };
public override bool CanConvert(Type objectType)
{
return objectType == typeof(bool?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string b = (string)reader.Value;
return b != null ? TrueStrings.Contains(b.ToLower()) : (bool?)null;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, swap out the [JsonConverter] attribute on your ExemptBenefits property declaration for a [JsonProperty] attribute that has its ItemConverterType parameter set to the simplified converter. Json.Net will then handle creating the list for you.
[JsonProperty(ItemConverterType = typeof(NullableBoolDeSerializer))]
public List<bool?> ExemptBenefits { get; set; }
Fiddle: https://dotnetfiddle.net/Dp4N11

Deserialize JSON to C#: string and string[] values [duplicate]

This question already has answers here:
How to handle both a single item and an array for the same property using JSON.net
(9 answers)
Closed 7 years ago.
I need to consume JSON from a REST service. For one of the fields, the service sometimes returns a string for the value yet other times returns an array of strings for the value.
For example:
var jsonArrayResponse = "{ \"id\" : \"abc\", \"relatedIds\" : [ \"def\", \"ghi\", \"jkl\" ] }";
var jsonSingleResponse = "{ \"id\" : \"123\", \"relatedIds\" : \"456\" }";
I have implemented the destination class using the SingleValueArrayConverter pattern found on this post. Here is the full set of code to illustrate my problem:
class Program
{
static void Main(string[] args)
{
// this works fine with the SingleValueArrayConverter
var jsonArray = "{ \"id\" : \"abc\", \"relatedIds\" : [ \"def\", \"ghi\", \"jkl\" ] }";
var objArray = (MyObject)JsonConvert.DeserializeObject(jsonArray, typeof(MyObject));
// this fails to parse "456" with Exception message:
// "Unable to cast object of type 'System.Object' to
// type 'System.Collections.Generic.List`1[System.String]'."
var jsonSingle = "{ \"id\" : \"123\", \"relatedIds\" : \"456\" }";
var objSingle = (MyObject)JsonConvert.DeserializeObject(jsonSingle, typeof (MyObject));
}
}
[DataContract]
public class MyObject
{
[DataMember(Name = "id")]
public string Id;
[DataMember(Name = "relatedIds")]
[JsonConverter(typeof(SingleValueArrayConverter<string>))]
public List<string> RelatedIds;
}
public class SingleValueArrayConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object retVal = new Object();
if (reader.TokenType == JsonToken.StartObject)
{
T instance = (T)serializer.Deserialize(reader, typeof(T));
retVal = new List<T>() { instance };
}
else if (reader.TokenType == JsonToken.StartArray)
{
retVal = serializer.Deserialize(reader, objectType);
}
return retVal;
}
public override bool CanConvert(Type objectType)
{
return false;
}
}
How can I fix this to properly parse the two different JSON blobs into the same class?
UPDATE:
I modified the SingleValueArrayConverter to check the string case (as mentioned below), and things started working. I use the converter for more than just strings, so I had to add the first if statement, rather than modify it as suggested).
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object retVal = new Object();
if (reader.TokenType == JsonToken.String)
{
string instance = (string)serializer.Deserialize(reader);
retVal = new List<string> () {instance};
}
else if (reader.TokenType == JsonToken.StartObject)
{
T instance = (T)serializer.Deserialize(reader, typeof(T));
retVal = new List<T>() { instance };
}
else if (reader.TokenType == JsonToken.StartArray)
{
retVal = serializer.Deserialize(reader, objectType);
}
return retVal;
}
public override bool CanConvert(Type objectType)
{
return false;
}
}
You could change this:
var jsonSingle = "{ \"id\" : \"123\", \"relatedIds\" : \"456\" }";
To this:
var jsonSingle = "{ \"id\" : \"123\", \"relatedIds\" : [ \"456\" ] }";
...and accept that relatedIds has one or more items therefore should always be a collection.
Alternatively you might make RelatedIds a generic:
public class MyObject<T>
{
// .... //
public T RelatedIds;
}
Which you could use like this:
var objArray = (MyObject<List<string>>)JsonConvert.DeserializeObject(jsonArray, typeof(MyObject<List<string>>));
var objSingle = (MyObject<object>)JsonConvert.DeserializeObject(jsonSingle, typeof (MyObject<object>));
Personally I prefer the previous option, for simplicity.
One thing you cannot do is make something defined as a string suddenly become a List<string> at runtime (actually maybe you could using dynamic, but better not to go there...). That is not allowed in a statically-typed language (though is possible in weakly typed languages such as JS or PHP).
Change JsonToken.StartObject to JsonToken.String. If you put a breakpoint right on the line object retVal = new Object(); you can see that reader.TokenType is JsonToken.String during the second instantiation. That's the only problem with your code.

Categories