I am using this to deserialize my Json response from an Api.
var apiResponse = await GetAsync<MyResponseModel>(request);
In my response model there is property that is an int, but the api for some reason formats it as a float.
So it looks like this:
{
"Quantity": 6.000
}
Now I parse it with this trick:
[JsonProperty("Quantity")]
private float QuantityFloat {
set => Quantity = IsInt(value) ? (int) value: throw new ArgumentException("Tried to parse number to Quantity that is not an int.");
}
public int Quantity { get; set; }
private static bool IsInt(float value)
{
var x = (int) value;
var temp2 = value - x;
return temp2 <= 0;
}
My linter now complains: "Properties with only setters are confusing and counterintuitive. Instead, a property getter should be added if possible, or the property should be replaced with a setter method."
So I was asking myself if there is a better more elegant way of doing this.
I would suggest creating a JsonConverter<int> that can handle both integer and decimal-formatted values:
(This solution is for System.Text.Json)
public class IntConverter : JsonConverter<int>
{
public override int Read(ref Utf8JsonReader reader, System.Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TryGetInt32(out var intVal))
{
return intVal;
}
// customize this part as necessary to satisfactorily deserialize
// the float value as int, or throw exception, etc.
float floatValue = reader.GetSingle();
return (int)floatValue;
}
public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
Then you can decorate your property with [JsonConverter(typeof(IntConverter))] which will cause the converter to be used during serialization:
public class Model
{
[JsonConverter(typeof(IntConverter))]
public int Quantity { get; set; }
}
Handling it like this takes away the need for any extra properties, thus simplifying your solution. This is especially true if you have to handle this kind of scenario in multiple places.
Try it online
JSON.NET Solution:
public class IntConverter : JsonConverter<int>
{
public override int ReadJson(JsonReader reader, Type objectType, int existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Float)
{
// customize this part as necessary to satisfactorily deserialize
// the float value as int, or throw exception, etc.
double value = (double)reader.Value;
return (int)value;
}
long intVal = (long)reader.Value;
return (int)intVal;
}
public override void WriteJson(JsonWriter writer, int value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
with the following model:
public class Model
{
[JsonConverter(typeof(IntConverter))]
public int Quantity { get; set; }
}
Try it online
Related
I am trying to convert this Newtonsoft.Json.JsonConverter to System.Text.Json. However, I was only able to use a single primitive type, say double and even there I cant apply the converter on nullable (double?). How can I convert this to support nullable and all number formats (float, double).
Newtonsoft.Json
public class DecimalRoundingJsonConverter : JsonConverter
{
private readonly int _numberOfDecimals;
public DecimalRoundingJsonConverter() : this(6)
{
}
public DecimalRoundingJsonConverter(int numberOfDecimals)
{
_numberOfDecimals = numberOfDecimals;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
double input = 0;
if (value is decimal)
{
var d = (decimal)value;
input = Convert.ToDouble(d);
}
else if (value is float)
{
var d = (float)value;
input = Convert.ToDouble(d);
}
else
{
input = (double)value;
}
var rounded = Math.Round(input, _numberOfDecimals);
writer.WriteValue((decimal)rounded);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(decimal);
}
}
System.Text.Json (basic)
public class DecimalRoundingJsonConverter : JsonConverter<double>
{
private readonly int _numberOfDecimals = 6;
public override double Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(
Utf8JsonWriter writer,
double dvalue,
JsonSerializerOptions options)
{
double input = (double)dvalue;
var rounded = Math.Round(input, _numberOfDecimals);
writer.WriteStringValue(rounded.ToString());
}
}
You can create a converter that applies to all float, double and decimal values, as well as nullables of the same, by creating a JsonConverter<object> and overriding JsonConverter<object>.CanConvert(Type) to return true only for the six relevant types.
The following does the job:
public class RoundingJsonConverter : RoundingJsonConverterBase
{
// The converter works for float, double & decimal. Max number of decimals for double is 15, for decimal is 28, so throw an exception of numberOfDecimals > 28.
public RoundingJsonConverter(int numberOfDecimals) => NumberOfDecimals = (numberOfDecimals < 0 || numberOfDecimals > 28 ? throw new ArgumentOutOfRangeException(nameof(numberOfDecimals)) : numberOfDecimals);
protected override int NumberOfDecimals { get; }
}
public class RoundingTo2DigitsJsonConverter : RoundingJsonConverterBase
{
protected override int NumberOfDecimals { get; } = 2;
}
public class RoundingTo6DigitsJsonConverter : RoundingJsonConverterBase
{
protected override int NumberOfDecimals { get; } = 6;
}
public abstract class RoundingJsonConverterBase : JsonConverter<object>
{
protected abstract int NumberOfDecimals { get; }
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
typeToConvert = Nullable.GetUnderlyingType(typeToConvert) ?? typeToConvert;
if (typeToConvert == typeof(decimal))
return reader.GetDecimal();
else if (typeToConvert == typeof(double))
return reader.GetDouble();
else if (typeToConvert == typeof(float))
return (float)reader.GetDouble();
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
switch (value)
{
case double d:
writer.WriteNumberValue(Math.Round(d, Math.Min(15, NumberOfDecimals)));
break;
case decimal d:
writer.WriteNumberValue(Math.Round(d, NumberOfDecimals));
break;
case float f:
writer.WriteNumberValue((decimal)Math.Round((decimal)f, NumberOfDecimals));
break;
default:
throw new NotImplementedException();
}
}
public override bool CanConvert(Type typeToConvert)
{
typeToConvert = Nullable.GetUnderlyingType(typeToConvert) ?? typeToConvert;
return typeToConvert == typeof(double) || typeToConvert == typeof(decimal) || typeToConvert == typeof(float);
}
}
Notes:
System.Text.Json has no equivalent to Newtonsoft's JsonConverter.CanRead so you must implement Read() as well as Write().
When adding the converter to JsonSerializerOptions.Converters use DecimalRoundingJsonConverter and pass the required number of digits as a constructor argument, e.g.:
var options = new JsonSerializerOptions
{
Converters = { new RoundingJsonConverter(6) },
};
However, if you are applying the converter via attributes, Microsoft does not allow passing of converter parameters (see here for confirmation) so you will need to create a specific converter type for each required number of digits, e.g.
public class RoundingTo2DigitsJsonConverter : RoundingJsonConverterBase
{
protected override int NumberOfDecimals { get; } = 2;
}
public class RoundingTo6DigitsJsonConverter : RoundingJsonConverterBase
{
protected override int NumberOfDecimals { get; } = 6;
}
And then apply e.g. as follows:
[JsonConverter(typeof(RoundingTo6DigitsJsonConverter))]
public decimal? SixDecimalPlaceValue { get; set; }
Nullable.GetUnderlyingType(Type) can be use used to get the underlying type of a nullable type such as decimal? or double?.
JsonConverter<T>.Write() is never called for null values of nullables unless JsonConverter<T>.HandleNull is overridden to return true.
Demo fiddle here.
A bit stuck, (confusing myself really, I think).
I am wanting to convert a value from a JSON string from it String representation of hex to an int. I only need to get the value I will never need to write the other way.
For instance
{
"name" : "someName",
"id" : "b1"
}
I have a class created
public class Person
{
public string name;
[JsonConverter(typeof(myConverter))]
public int id;
}
and my converter (I am not sure I understand this part correctly)
public class myConverter : JsonConverter
{
//CanConvert Boiler Plate
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string sValue = existingValue.ToString().ToUpper();
int iValue = Convert.ToInt32(sValue);
return iValue;
}
I will of course add additional checks to validate data and what not, but I wanted to start with something to see how it worked.
So with the above example I would want my int to be 177
Any help, guidance would be appreciated.
Try following Command to Convert a Hex-Number to an int
var hex = "b1"
int intValue = int.Parse(hex, System.Globalization.NumberStyles.HexNumber);
Are you using Newtonsoft.Json? I notice your code wouldn't work on .NET System.Text.Json since:
There is no public non-generic version of JsonConverter.
It wouldn't work on id because it's not a property.
Here's the working code for System.Text.Json:
public class HexConverter : JsonConverter<int>
{
public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = reader.GetString();
return Convert.ToInt32(value, 16);
}
public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
The class has to contain properties to be deserialized:
public class Person
{
public string name { get; set; }
[JsonConverter(typeof(HexConverter))]
public int id { get; set; }
}
Usage:
const string json = #"{
""name"" : ""someName"",
""id"" : ""b1""
}";
var person = JsonSerializer.Deserialize<Person>(json, new JsonSerializerOptions());
Console.WriteLine(person!.id);
// Result: 177
This case Convert.ToInt32 works for me, but you can also use int.Parse as suggested in the other answer.
How can i set FloatParseHandling.Decimal for a custom JsonConverter?
we have a struct DecimalDbValue that internally only holds one decimal field that i want to be de/serialized for all its types.
It uses a magic number (decimal.MinValue) for indicating a "null" value. It was created before .net 2.0 having nullable value types!
This is a spripped down version of our struct::
[Serializable]
[JsonConverter(typeof(DecimalDbValueJsonConverter))]
public struct DecimalDbValue : ISerializable
{
private readonly Decimal _decValue;
public DecimalDbValue(
decimal init)
{
_decValue = init;
}
[JsonConstructor]
public DecimalDbValue(
decimal? init)
{
if (init.HasValue)
_decValue = init.Value;
else
_decValue = decimal.MinValue;
}
private DecimalDbValue(
SerializationInfo objSerializationInfo,
StreamingContext objStreamingContext)
{
_decValue = objSerializationInfo.GetDecimal("value");
}
public bool IsNotNull
{
get
{
return !IsNull;
}
}
public bool IsNull
{
get
{
return _decValue.Equals(Decimal.MinValue);
}
}
public Decimal Value
{
get
{
return _decValue;
}
}
public void GetObjectData(
SerializationInfo info,
StreamingContext context)
{
info.AddValue("value", _decValue);
}
}
I created a JsonConverter:
class DecimalDbValueJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(DecimalDbValue) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = reader.Value == null ? (decimal?)null : Convert.ToDecimal(reader.Value);
return new DecimalDbValue(value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var dbValue = (DecimalDbValue)value;
if (dbValue.IsNull)
writer.WriteNull();
else
writer.WriteValue(dbValue.Value);
}
}
and set attribute [JsonConverter(typeof(DecimalDbValueJsonConverter))] on the DecimalDbValue struct
I have added a test:
[Test]
public void TestMaxDecimalDbValue()
{
var s = new DecimalDbValue(decimal.MaxValue);
var json = JsonConvert.SerializeObject(s, Formatting.Indented);
var x = JsonConvert.DeserializeObject<DecimalDbValue>(json);
Assert.AreEqual(s, x);
}
but it throws:
System.OverflowException : Value was either too large or too small for a Decimal.
How can i set FloatParseHandling.Decimal for the JsonConverter? How to make it work for MaxValue as well? Is there any other way?
Actually i would like to have it serialize/deserialized exactly like an decimal? (nullable decimal)
Thanks
It seems to be impossible.
Therefor i removed the JsonConverter completely.
Set this attribute on the struct:
[JsonObject(MemberSerialization.OptIn)]
as well as this on the constructor:
[JsonConstructor]
public DecimalDbValue(
decimal? init) //IMPORTANT: if you change the name of init - rename the JsonProperty below too - you might break all existing json out there thou!
as well as this on the get property:
[JsonProperty("init")]
public Decimal Value
{
get
{
return _decValue;
}
}
Unfortunately this bloats the json:
{
"init": 79228162514264337593543950335.0
}
In case it helps, you can set it for the whole JsonSerializer:
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
FloatParseHandling = FloatParseHandling.Decimal
};
_innerSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings);
You might also want to have a look on FloatFormatHandling to handle your nullables.
https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_FloatFormatHandling.htm
I have a custom class, which I am serializing using JSON.NET.
In that class, lets call it posInvoice, I have properties such as UnitPrice, Quantity, and TotalAmount which are of type string. This works fine when I send the serialized object to an endpoint as a POST Request.
Now I have another endpoint, which accepts the same class, posInvoice. However, this endpoint is expecting these values to be decimals instead.
What would be the best way to handle this in my code? Should I just create another class and change the property types? I have researched and tried to look for a similar situation in Stack Overflow, but couldn't find anything.
Here's the approach I would take:
Define your PosInvoice class using decimals, since that is the most appropriate data type for the amounts.
Create a custom JsonConverter class which can be used during serialization to convert the decimals to strings.
Use the converter with the endpoint which requires the amounts as strings; omit it otherwise.
Model class:
public class PosInvoice
{
public string Description { get; set; }
public decimal UnitPrice { get; set; }
public decimal Quantity { get; set; }
public decimal TotalAmount { get; set; }
}
Converter:
public class InvoiceAmountsAsStringsConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(PosInvoice);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
PosInvoice invoice = (PosInvoice)value;
JsonObjectContract contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(typeof(PosInvoice));
writer.WriteStartObject();
foreach (JsonProperty prop in contract.Properties)
{
writer.WritePropertyName(prop.PropertyName);
object propValue = prop.ValueProvider.GetValue(invoice);
if (propValue is decimal)
{
writer.WriteValue(((decimal)propValue).ToString(CultureInfo.InvariantCulture));
}
else
{
serializer.Serialize(writer, propValue);
}
}
writer.WriteEndObject();
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Usage:
public static string SerializeInvoice(PosInvoice invoice, bool serializeDecimalsAsStrings)
{
var settings = new JsonSerializerSettings { Formatting = Formatting.Indented };
if (serializeDecimalsAsStrings)
{
settings.Converters.Add(new InvoiceAmountsAsStringsConverter());
}
return JsonConvert.SerializeObject(invoice, settings);
}
Working demo here: https://dotnetfiddle.net/4beAW3
I am receiving json from a server which I am converting to an object using Json.Net. For one member I am using the StringEnumConverter which works really perfect.
However all of a sudden the server decided to use strings which also can start with a number which results in a JsonSerilizationException - obviously because enums cannot start with a number in .Net.
Now I am trying to find a solution to handle that.My first approach was to add a "_" when Reading the Json (so my enums in the code would have a starting _ when they are followed by a number) and when writing the json I would delete the starting _ (if a number is following). To achieve this I copied the StringEnumConverter into my namespace and tried to change the according part in the WriteJson and ReadJson methods. However I cannot use the StringEnumConverter since there are other dependencies I cannot access in my own namespace.
Is there any elegant solution to this problem?
You can create a JsonConverter and trim the digits from the front
public class Json_34159840
{
public static string JsonStr = #"{""enum"":""1Value"",""name"":""James"",""enum2"":""1""}";
public static void ParseJson()
{
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new EnumConverter() }
};
// Later on...
var result = JsonConvert.DeserializeObject<JsonClass>(JsonStr);
Console.WriteLine(result.Enum);
Console.WriteLine(result.Enum2);
Console.WriteLine(result.Name);
}
}
public class EnumConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var str = value.ToString();
if (Regex.IsMatch(str, #"^_"))
{
writer.WriteValue(str.Substring(1));
}
else
{
writer.WriteValue(str);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = reader.Value.ToString();
if (Regex.IsMatch(value, #"^\d+$"))
{
return Enum.Parse(objectType, value);
}
if (Regex.IsMatch(value, #"^\d+"))
{
value = "_" + value;
}
return Enum.Parse(objectType, value);
}
public override bool CanConvert(Type objectType)
{
//You might want to do a more specific check like
//return objectType == typeof(JsonEnum);
return objectType.IsEnum;
}
}
public enum JsonEnum
{
_0Default,
_1Value
}
public class JsonClass
{
public string Name { get; set; }
public JsonEnum Enum { get; set; }
public JsonEnum Enum2 { get; set; }
}
Hope this helps.
EDIT: Added support for integers :D
A simple strategy is to deserialize the value into a string property and then convert into your own data type (enum or otherwise) via an accessor method or a secondary getter-only property.