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.
Related
I'm looking for a way to transform this c# object:
class BaseClass
{
public string Value1 {get; set;}
public NestedObject nestedObject {get;set;}
}
class NestedObject
{
public string NestedValue1 {get; set;}
}
Into this json:
{
"Value1": "value1",
"NestedObject_NestedValue1": "nestedValue1"
}
By concatening the names of the nested parameters to their parent's name
Using normal serialization, this code: var json= JsonConvert.SerializeObject(baseClass);
Would instead return a json like this one:
{
"Value1": "value1",
"NestedObject": {
"NestedValue1": "nestedValue1"
}
}
I am sceptical about there being a way to deserialize a json like that back to an object tho.
Update:
As some asked what is the reason I'm trying to accomplish this:
The reason I asked this question is because I serialize this object to send as json metadata to a service that only allows referencing top level propreties in a way similar to this:
[Metadata_Value1] would return "value1"
However [Metadata_NestedObject_NestedValue1] doesn't work and there isn't any indication to there being a way to reference nested properties.
Taking this in consideration I hoped there would be some solution that would allow keeping the nested objects in my program but transforming them all to top properties when sending them to this service.
In the service I would then be able to do: [NestedObject_NestedValue1] and get the value "nestedValue1"
You can utilize a custom converter that looks like below :
public class NestedJsonConverter : JsonConverter
{
private readonly Type[] _types;
public NestedJsonConverter(params Type[] types)
{
_types = types;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
writer.WriteStartObject();
void writeNested(JObject target, object source, string prefix)
{
target.Properties().ToList().ForEach(p =>
{
var prop = source.GetType().GetProperty(p.Name);
var value = prop.GetValue(source);
var prefixed = string.IsNullOrEmpty(prefix) ? p.Name : $"{prefix}_{p.Name}";
if (p.Value.Type == JTokenType.Object)
{
writeNested((JObject)p.Value, value, prefixed);
}
else if (p.Value.Type == JTokenType.Array)
{
// you may need a more advanced handling in array scenarios
var arr = (JArray)p.Value;
writer.WritePropertyName(prefixed);
arr.WriteTo(writer);
}
else
{
writer.WritePropertyName(prefixed);
writer.WriteValue(value);
}
}
);
}
writeNested(o, value, "");
writer.WriteEndObject();
}
}
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 bool CanRead
{
get
{
return false;
}
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == objectType);
}
}
Dotnetfiddle
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:
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 5 years ago.
I have some JSON that has an INT result, that may return as a single INT, or a LIST of INT's.. ie:
"ErrorCode":[3]
or
"ErrorCode": 1
How do I get that to deserialize with Newstonsoft.Json's JsonConvert?
If I define the object as a LIST, it fails when the results is a single INT, and vice versa.
I would think you would need to settle on the data type for your json. Perhaps always have it return an array so that there is never just an INT. So, your json would look like this:
"ErrorCode":[3]
Then any single error code would just be an array of size 1.
You can create a class like:
class MyResult {
public object ErrorCode {get; set;}
}
And use it to deserialize your json:
MyResult r = JsonConvert.Deserialize<MyResult>(json);
List<int> errorCodes;
if (r.ErrorCode is JArray) {
errorCodes = (r.ErrorCode as JArray).ToObject<List<int>>();
// ...
} else {
errorCodes = new List<int> { Convert.ToInt32(r.ErrorCode) };
// ...
}
Hope it helps.
You can create a custom json convertor :
class ResolveListConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
List<T> list = new List<T>();
if (reader.TokenType == JsonToken.StartArray)
{
reader.Read();
while (reader.Value != null)
{
list.Add(Converter(reader.Value));
reader.Read();
}
}
else if(reader.Value != null)
{
list.Add(Converter(reader.Value));
}
return list;
}
public T Converter(object obj) => (T)Convert.ChangeType(obj, typeof(T));
public override bool CanConvert(Type objectType) => true;
}
then you can deserialize your json :
var result = JsonConvert.DeserializeObject<List<int>>(json, new ResolveListConverter<int>());
I have this kind of class:
class Thing
{
// ...
public IDictionary<string, dynamic> Parameters { get; set; }
}
//...
void Test()
{
Thing thing = new Thing();
thing.Paramaters["counting"] = new List<int>{1,2,3};
thing.Parameters["name"] = "Numbers";
thing.Parameters["size"] = 3;
string result = JsonConvert.SerializeObject(thing);
}
And I'd like to result to contain this:
{
"Parameters" :
{
"counting" : "[1,2,3]",
"name" : "Numbers",
"size" : 3
}
}
I have taken a look at IContractResolver and believe I should special case strings on deserialization to check if there's JSON in them, and special case all class objects to convert them to a string. I just have no idea where to begin doing that.
In the end the problem is this: the data structure I'm plugging this JSON into does not work with nested JSON. It only knows about the basic data, i.e. string and numbers, at this "sublevel". I know, terrible, get rid of this evil data structure. Well, I can't. So I need to be creative and I thought this might be a way out. If anyone can think of a better way, I'd much appreciate it!
EDIT The answers below special case the List<int> example I put in Test, but it's still a Dictionary<string, dynamic> which can contain everything. That's what I mean with nested JSON: any JSON, not just an array.
I understand the problem is not so much about writing a List to a string, but more about a way to create JSON with only two levels of depth - having everything beyond that a string.
I don't think an IContractResolver would work for this, you should implement a JsonConverter instead. The basic idea would be that it iterates over your object's children, then over their children, checking their type. If they're an array or an object - it would replace them with a serialized string.
Implementation:
class TwoDepthJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var jo = JObject.FromObject(value);
foreach (var property in jo)
{
foreach (var parameter in property.Value)
{
var paramVal = parameter.First;
if (paramVal.Type == JTokenType.Array || paramVal.Type == JTokenType.Object)
{
paramVal.Replace(JsonConvert.SerializeObject(paramVal.ToObject<object>()));
}
}
}
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JToken.ReadFrom(reader).ToObject(objectType);
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
Usage:
Thing thing = new Thing();
thing.Parameters["counting"] = new List<int> { 1, 2, 3 };
thing.Parameters["name"] = "Numbers";
thing.Parameters["size"] = 3;
string result = JsonConvert.SerializeObject(thing, Formatting.Indented, new TwoDepthJsonConverter());
// Results:
// {
// "Parameters": {
// "counting": "[1,2,3]",
// "name": "Numbers",
// "size": 3
// }
// }
Of course, performance could be improved - for example writing to the writer manually instead of parsing to a JObject and then manipulating it. However this should be a good starting point.
You can create a JsonConverter and instead of using List<T> you would use a custom class that inherits from List<T>..
The reason for it needs to be a custom List is that if you register a JsonConverter for List<T> you would not be able to get normal array serialization.
Following http://blog.maskalik.com/asp-net/json-net-implement-custom-serialization/ I was able to make this.
public class CustomListSerializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var values = (IEnumerable)value;
var items = values.Cast<object>().ToList();
var s = JsonConvert.SerializeObject(items);
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.IsAssignableFrom(typeof(IEnumerable));
}
}
[JsonConverter(typeof(CustomListSerializer))]
internal class CustomList<T> : List<T>
{
}
class Program
{
static void Main(string[] args)
{
var parameters = new Dictionary<string, object>();
parameters.Add("counting", new CustomList<int>() { 1, 2, 3, 5 });
parameters.Add("users", new CustomList<User>() { new User { Name = "TryingToImprove" }, new User { Name = "rubenvb" } });
parameters.Add("name", "Numbers");
parameters.Add("size", 4);
var thing = new
{
Parameters = parameters,
Name = "THING",
Test = new List<int>() { 1, 2, 3}
};
Console.WriteLine(JsonConvert.SerializeObject(thing));
}
}
internal class User
{
public string Name { get; set; }
}
which will return
{
"Parameters": {
"counting": "[1,2,3,5]",
"users": "[{\"Name\":\"TryingToImprove\"},{\"Name\":\"rubenvb\"}]",
"name": "Numbers",
"size": 4
},
"Name": "THING",
"Test": [1, 2, 3]
}
This is working -
internal class CustomJsonFormatter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsAssignableFrom(typeof(Thing));
}
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)
{
var data = value as Thing;
foreach (var prop in data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
writer.WriteStartObject();
writer.WritePropertyName(prop.Name);
writer.WriteStartObject();
var local = prop.GetValue(data, null) as Dictionary<string, object>;
foreach (var key in local.Keys)
{
writer.WritePropertyName(key);
if (local[key].GetType() == typeof(List<int>))
{
string s = "[";
var arr = ((List<int>)local[key]);
for (var i = 0; i < arr.Count; i++)
{
s += arr[i].ToString() + ",";
}
writer.WriteValue(s.Substring(0, s.Length - 1) + "]");
}
else { writer.WriteValue(local[key]); }
}
}
writer.WriteEndObject();
writer.WriteEndObject();
}
var settings = new JsonSerializerSettings();
settings.Converters.Add(new CustomJsonFormatter());
string result = JsonConvert.SerializeObject(thing, settings);
produces
{"Parameters":{"counting":"[1,2,3]","name":"Numbers","size":3}}
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);
}