I'm querying a service which returns a list of tags in multiple formats:
{
"tags": "a,b,c"
}
or
{
"tags": "a b c"
}
or
{
"tags": "a+b+c"
}
The object that I want to deserialize this to is a List<string>. I've written a TagsConverter that implements the necessary methods in JsonConverter like so:
public class TagsConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(string));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return reader
.Value
.ToString()
.Split(' ')
.ToList();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var list = (List<string>)value;
var delimitedList = string.Join(" ", list);
writer.WriteValue(delimitedList);
}
}
Then I annotate the field in the object like so:
public class Foo
{
[JsonConverter(typeof(TagsConverter))]
public List<string> Tags { get; set; }
}
And this works, but only for tags that are separated by spaces.
However the TagsConverter can work in all 3 scenarios if I'm able to just change the Split and the Join functions in a parameterized fashion. But since we pass only the type to the JsonConverter attribute, I understand that we can't pass a "delimiter" as an argument there.
Is there any way to achieve this?
There is an additional JsonConverter constructor overload that takes both a type and a params object[] converterParameters that is injected into the concrete instance of your converter. You can take advantage of this overload to maximize your reusability.
public class TagsConverter : JsonConverter
{
private readonly string _delimiter;
public TagsConverter(string delimiter)
{
_delimiter = delimiter;
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(string));
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
return reader
.Value
.ToString()
.Split(_delimiter.ToCharArray())
.ToList();
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
var list = (List<string>)value;
var delimitedList = string.Join(_delimiter, list);
writer.WriteValue(delimitedList);
}
}
Utilizing the overload is as easy as passing it as a second parameter:
public class Foo
{
[JsonConverter(typeof(TagsConverter), " ")]
public List<string> Tags { get; set; }
}
If only needed for the reading/deserialization of the JSON do note that string.Split can take a character array char[]
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return reader
.Value
.ToString()
.Split(new char[]{' ', ',', '+'}, StringSplitOptions.RemoveEmptyEntries)
.ToList();
}
Which means that the converter can handle all three delimiters when reading.
Related
Same question as Deserializing stringified (quote enclosed) nested objects with Jackson but for C# and Newtonsoft Json.Net. The question is how to deserialize a nested string representation of JSON using Json.Net. Example response from REST endpoint:
{
"Id": "abcd1234",
"Foo": "{\"field1\": "abc", \"field2\": \"def\"}"
}
C# Model:
public class Model {
public Guid Id { get; set; }
public Dictionary<string, string> Foo { get; set; }
}
Just calling JsonConvert.DeserializeObject<Model>(returnedJson) will error out because Foo in its returned form is stringified JSON, aka just a string. This is the case for other fields in our JSON response as well that have different Dictionary types associated in their models (e.g. Dictionary<string, int> and Dictionary<string, DateTime>).
What's the simplest way to handle this? Is a custom JsonConverter necessary or is there some built-in way to handle it?
A JsonConverter is probably the simplest way to handle this implicit conversion. Here's one that works for your case.
public class MyConverter: JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JsonConvert.DeserializeObject(reader.Value as string, objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And then you decorate that property with the attribute like this:
public class Model
{
public string Id { get; set; }
[JsonConverter(typeof(MyConverter))]
public Dictionary<string, string> Foo { get; set; }
}
Just to follow up, I used a very similar solution to what #VijayeR recommended, but needed to tweak it a bit as it was still causing some problems for me. One issue is that when using the decorator, apparently CanConvert doesn't get called at all because Json.Net assumes you've assigned the correct converter to that property. This is what worked for me:
public class NestedJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jToken = JToken.Load(reader);
if (jToken.Type == JTokenType.String)
{
jToken = JToken.Parse((string) jToken);
}
return jToken.ToObject(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanWrite => false;
}
Same question as Deserializing stringified (quote enclosed) nested objects with Jackson but for C# and Newtonsoft Json.Net. The question is how to deserialize a nested string representation of JSON using Json.Net. Example response from REST endpoint:
{
"Id": "abcd1234",
"Foo": "{\"field1\": "abc", \"field2\": \"def\"}"
}
C# Model:
public class Model {
public Guid Id { get; set; }
public Dictionary<string, string> Foo { get; set; }
}
Just calling JsonConvert.DeserializeObject<Model>(returnedJson) will error out because Foo in its returned form is stringified JSON, aka just a string. This is the case for other fields in our JSON response as well that have different Dictionary types associated in their models (e.g. Dictionary<string, int> and Dictionary<string, DateTime>).
What's the simplest way to handle this? Is a custom JsonConverter necessary or is there some built-in way to handle it?
A JsonConverter is probably the simplest way to handle this implicit conversion. Here's one that works for your case.
public class MyConverter: JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JsonConvert.DeserializeObject(reader.Value as string, objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And then you decorate that property with the attribute like this:
public class Model
{
public string Id { get; set; }
[JsonConverter(typeof(MyConverter))]
public Dictionary<string, string> Foo { get; set; }
}
Just to follow up, I used a very similar solution to what #VijayeR recommended, but needed to tweak it a bit as it was still causing some problems for me. One issue is that when using the decorator, apparently CanConvert doesn't get called at all because Json.Net assumes you've assigned the correct converter to that property. This is what worked for me:
public class NestedJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jToken = JToken.Load(reader);
if (jToken.Type == JTokenType.String)
{
jToken = JToken.Parse((string) jToken);
}
return jToken.ToObject(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanWrite => false;
}
I'm serializing decimals using Newtonsoft.Json.JsonSerializer.
How can I set it to serialize decimal numbers with only 1 decimal place to use 0 at the end.
i.e. 3.5 serializes to "3.50"?
You'll have to write your own custom JsonConverter and use it to intercept the decimal type so you can change how it gets serialized. Here's an example:
public class DecimalFormatConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(decimal));
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
writer.WriteValue(string.Format("{0:N2}", value));
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use it, simply pass in a new instance of this custom converter to the SerializeObject method:
var json = JsonConvert.SerializeObject(yourObject, new DecimalFormatConverter());
The accepted answer is correct, but expanding upon the comments on accepted answer:
If you want the decimals in your JSON to be numbers instead of strings, you need to use WriteRawValue and use :0.00 instead of :N2 for the string formatting (as N2 includes thousand separator commas and other culture specific number formatting that will break your JSON)
public class DecimalFormatConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(decimal);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue($"{value:0.00}");
}
public override bool CanRead => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This is the difference in output compared to the accepted answer.
# writer.WriteRawValue($"{value:0.00}");
{
"MyDecimal": 3.50,
"MyBiggerDecimal": 12345.50
}
# writer.WriteValue($"{value:N2}");
{
"MyDecimal": "3.50",
"MyBiggerDecimal": "12,345.50"
}
Note - the accepted answer is correct for the OP's specific question i.e. serialize 3.5 to "3.50", but I got here wanting to serialize 3.5 to 3.50 (without the string quotes).
Hy!
The second answer is also correct, but not reflecting Cultures.
If you want to have really 0.00 (with .) you have to use:
public class DecimalFormatConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(decimal);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
FormattableString formattableString = $"{value:0.00}";
writer.WriteRawValue(formattableString.ToString(CultureInfo.InvariantCulture));
}
public override bool CanRead => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
With this it is garantueed to have dot as decimal separater.
The difference is:
FormattableString formattableString = $"{value:0.00}";
writer.WriteRawValue(formattableString.ToString(CultureInfo.InvariantCulture));
Why my custom JsonConverter.WriteJson() method doesn't get called ?
class MyType{
[JsonConverter(typeof(DocumentXamlDeserializer))]
public string GuiData { get; set; }
public string SimpleString;
}
Although the ReadJson does get called:
public class DocumentXamlDeserializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(Gui.Handler.SerializeData());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var xaml = reader.Value as string;
Gui.Handler.DeserializeData(xaml);
return xaml;
}
public override bool CanConvert(Type objectType) { return typeof(string).IsAssignableFrom(objectType); }
}
The serialization call: JsonConvert.SerializeObject(dataModel, Formatting.Indented);
The deserialization call: JsonConvert.DeserializeObject<Model>(raw);
Apparently this is because GuiData is null... I guess I could specify:
TypeNameHandling = TypeNameHandling.Objects
But I want to serialize only GuiData even if it's null (I set its value during serialization), without serializing all null-properties... well, if I don't find better way, I guess I'd have to suffice with it...
I notice there are some other results on stackoverflow for this question but they don't seem to work or are vague. Using the most popular result, I have put together this:
The problem is that when JSON comes back and is being serialised into one of my custom types, one of the bits of JSON is sometimes an array, and sometimes just a string. If my custom type has a string, and the JSON is an array, I get an error. The same happens the other way around, if the JSON is an object and my custom type is an array. It just can't map it to the property.
I decided to solve this, I want to override the deserialisation of this particular property, and if it's an object, I want to convert it into an array of 1 object.
In the object I am serialising to, I added a JsonConverter which I think is going to override the way it's deserialised.
[JsonConverter(typeof(ArrayOrSingleObjectConverter<string>))]
public List<string> person { get; set; }
The idea is that the custom converter will convert a single object to an array. So if the JSON is "Hello" it will set person to be a List containing "Hello" instead of throwing an exception saying cannot convert string to List.
If it's already an array, it should just leave it alone.
The converter looks like this:
public class ArrayOrSingleObjectConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true; // Not sure about this but technically it can accept an array or an object, so everything is game.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType == typeof(List<T>))
{
return serializer.Deserialize<List<T>>(reader);
}
else
{
var singleObject = serializer.Deserialize<T>(reader);
var objectAsList = new List<T>();
objectAsList.Add(singleObject);
return objectAsList;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
It doesn't work. The above code throws an exception trying to deserialize a a single string saying it can't cast it into a List inside the if statement (the 'objectype' is however a List).
I'm struggling to understand exactly what the read and write methods are doing. In the other answer on stackoverflow, it suggests throwing a NotImplementedException in the read method. But if I do that, the read method is called and the exception throws.
I think I'm on the right track, but I need a nudge in the right direction. I think I'm a little confused about what the ReadJSon method is doing and what its parameters mean.
I don't really understand where the value is coming from that it's deserializing since I didn't specify it in the Deserialize method call.
I'm a bit out of my depth on this one.
I had to do something similar last week and I came up with the following, which works fine for a List rather than an array
internal class GenericListCreationJsonConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<T>>(reader);
}
else
{
T t = serializer.Deserialize<T>(reader);
return new List<T>(new[] { t });
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
I like this method which makes Json.NET do all the heavy lifting. And as a result, it supports anything that Json.NET supports (List<>, ArrayList, strongly-typed arrays, etc).
public class FlexibleCollectionConverter : 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)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize(reader, objectType);
}
var array = new JArray(JToken.ReadFrom(reader));
return array.ToObject(objectType);
}
public override bool CanConvert(Type objectType)
{
return typeof (IEnumerable).IsAssignableFrom(objectType);
}
}