We have upgraded Newtonsoft.Json from 10.0.3 to 11.0.1 and code that has been working previously does not work anymore.
We have following JSON:
[{"mail-type":"registration","response":"250 OK id=1UjnNr-gdf-C0 ","invoice-id":,"email":"testuser08#test.com"}]
and we call following method:
var events = JsonConvert.DeserializeObject<Event[]>(jsonEvents);
This worked fine on 10.0.3, but does not on 11.0.1. On this version following exception is thrown:
Exception thrown: 'Newtonsoft.Json.JsonReaderException' in
Newtonsoft.Json.dll
Additional information: An undefined token is not a valid
System.Nullable`1[System.Int64]. Path '[0].invoice-id', line 1.
I tried to pass following options to DeserializeObject
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore
};
But still the same error. What changes have to be done to make it work on 11.0.1. I am affraid we cannot accomodate JSON output as this comes from third party API.
Your JSON sample is not well-formed. If I upload your JSON to https://jsonlint.com/ then the following error is generated:
Error: Parse error on line 4:
...0 ", "invoice-id": , "email": "testuse
----------------------^
Expecting 'STRING', 'NUMBER', 'NULL', 'TRUE', 'FALSE', '{', '[', got ','
The line in question is as follows:
"invoice-id": ,
According to the JSON standard there needs to be a value between the : and the ,. But, why did this work in Json.NET 10.0? Apparently it was a bug, which was fixed. According to the 11.0.1 release notes:
Fix - Fixed not erroring when reading undefined for nullable long
So if we assume that your Event type looks like this:
public partial class Event
{
[JsonProperty("invoice-id")]
public long? InvoiceId { get; set; }
// Other properties as required
}
Then in 10.0 your JSON could be deserialized successfully using this type, but in 11.0 it cannot. If, however, we change InvoiceId to be an int?:
public partial class Event
{
[JsonProperty("invoice-id")]
public int? InvoiceId { get; set; }
// Other properties as required
}
It fails in both versions. Thus the fix appears to have been to handle int? and long? consistently.
Ideally, you should ask whoever sent you such JSON to fix it so that it is well-formed as defined by http://www.json.org/ and RFC 8259. If you nevertheless need to parse such JSON in the same manner as Json.NET 10.0, you could introduce TolerantNullableLongConverter as follows:
public class TolerantNullableLongConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(long?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType == JsonToken.Undefined)
return null;
if (reader.Value is long)
return reader.Value;
// string or int or decimal or ...
return serializer.Deserialize<long>(reader);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And apply it to your type as follows:
public partial class Event
{
[JsonProperty("invoice-id")]
[JsonConverter(typeof(TolerantNullableLongConverter))]
public long? InvoiceId { get; set; }
// Other properties as required
}
You can implement workaround with custom converter:
internal class NullableLongFixupConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteValue(value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
// if token undefined - return null
if (reader.TokenType == JsonToken.Undefined)
return null;
// otherwise - value
return (long?) reader.Value;
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(long?);
}
}
Then you can either decorate properties with it:
class Event {
[JsonProperty("invoice-id")]
[JsonConverter(typeof(NullableLongFixupConverter))]
public long? InvoiceId { get; set; }
}
or register globally (will be invoked only for properties of type long?):
JsonConvert.DefaultSettings = () =>
{
var s = new JsonSerializerSettings();
s.Converters.Add(new NullableLongFixupConverter());
return s;
};
Related
I am trying to deserialize the following JSON (which validates on https://jsonlint.com/):
{"pandoc-api-version":[1,22],"meta":{"title":{"t":"MetaBlocks","c":[{"t":"Para","c":[{"t":"Str","c":"Dynamic"},{"t":"Space"},{"t":"Str","c":"Language"},{"t":"Space"},{"t":"Str","c":"Runtime"}]},{"t":"Para","c":[]}]}},"blocks":[{"t":"Para","c":[{"t":"Strong","c":[{"t":"Str","c":"Bill"},{"t":"Space"},{"t":"Str","c":"Chiles"},{"t":"Space"},{"t":"Str","c":"and"},{"t":"Space"},{"t":"Str","c":"Alex"},{"t":"Space"},{"t":"Str","c":"Turner"}]}]},{"t":"Para","c":[{"t":"Emph","c":[{"t":"Strong","c":[{"t":"Str","c":"Reading"},{"t":"Space"},{"t":"Str","c":"this"},{"t":"Space"},{"t":"Str","c":"Document:"}]}]}]}]}
into the following classes:
internal record TagContent(string T, OneOf<TagContent[], string>? C);
internal class RawPandoc {
[JsonProperty] public int[] PandocApiVersion = default!;
[JsonProperty] public Dictionary<string, TagContent> Meta = default!;
[JsonProperty] public TagContent[] Blocks = default!;
}
using the following code:
var settings = new JsonSerializerSettings {
ContractResolver = new DefaultContractResolver { NamingStrategy = new KebabCaseNamingStrategy() },
Converters = new JsonConverter[] { new OneOfJsonConverter() }
};
var pandoc = JsonConvert.DeserializeObject<RawPandoc>(s, settings);
and I get the following error:
Unexpected token when deserializing object: StartObject. Path 'meta.title.c[0]', line 1, position 69.
How can I resolve this?
For completeness, here is the current and incomplete code for OneOfJsonConverter. OneOf is a library for union types in C#:
using OneOf;
namespace PandocFilters {
public class OneOfJsonConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) {
if (value is IOneOf of) {
value = of.Value;
}
serializer.Serialize(writer, value);
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
if (reader.Value is null) { return null; }
// TODO not implemented yet
return reader.Value;
}
public override bool CanConvert(Type objectType) => objectType.UnderlyingIfNullable().GetInterfaces().Contains(typeof(IOneOf));
}
}
Problem is you are not advancing the reader in your ReadJson implementation. You declared your converter can handle IOneOf objects, and so JSON.NET expects your converter to actually read and handle it, however it does nothing as of now. So ReadJson is called (at the start of first array in json which should be deserialized to OneOf), and then after it returns - reader position is still where it was before (at start of array), which is not what JSON.NET expects. Then it fails trying to continue reading next object, because its assumptions are violated. So, just implement ReadJson, and meanwhile you can advance a reader for example like that:
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
// advance reader as expected
var eitherStringOrArray = JObject.ReadFrom(reader);
return reader.Value;
}
I am trying to deserialize a Unix timestamp to a DateTime. In my case, I need to do much more checks before I can set a property to DateTime from a timestamp. If I use DateTime from Newtonsoft.Json it deserializes it to UTC time and I need to deserialize it to a specific timezone
The problem is that I am not able to get the correct time. It seems like my string to long parsing is failing. If I can get the long unix timestamp, I can get the rest of the logic working
I have a class named Alert
class Alert
{
// Some properties
[JsonConverter(typeof(UnixTimestampJsonConverter))]
public DateTime Created { get; set; }
// Some more properties
}
the class UnixTimestampJsonConverter is
class UnixTimestampJsonConverter : JsonConverter
{
// Other override methods
public override object ReadJson (JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.EndObject)
return null;
if (reader.TokenType == JsonToken.StartObject) {
long instance = serializer.Deserialize<long> (reader);
return TimeUtils.GetCustomDateTime (instance);
}
return null;
}
}
Where TimeUtils.GetCustomDateTime (instance) takes the long unixtimestamp and converts it into DateTime object of specific timezone.
I am in a PCL library with Profile 78, so I have limited access to System.TimeZoneInfo and I am using PCL version of NodaTime for other timezone calculations.
In case anyone is interested, this is the project on Github - MBTA Sharp
I'm pretty sure all you need to do is call serializer.Deserialize. Doing this will advance the reader correctly and you shouldn't need to do anything else:
public class UnixTimestampJsonConverter : JsonConverter
{
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
long ts = serializer.Deserialize<long>(reader);
return TimeUtils.GetMbtaDateTime(ts);
}
public override bool CanConvert(Type type)
{
return typeof(DateTime).IsAssignableFrom(type);
}
public override void WriteJson(
JsonWriter writer,
object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanRead
{
get { return true; }
}
}
Example: https://dotnetfiddle.net/Fa8Zis
I have a custom JsonConverter, which doesn't seem to be called correctly. I have created the converter, added it to the JsonSerializerSettings.Converters collection and marked the property on the entity I am serialising with [JsonConverter(typeof(SearchGeoConverter))], but even with these in place the converters CanConvert method never sees the type I am trying to convert. I only ever see, string, int and JObject.
My converter looks like this:
public class SearchGeoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(DbGeography).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var point = (DbGeography) value;
var rawJson = string.Format("{{ \"type\": \"Point\", \"coordinates\": [{0}, {1}] }}", point.Latitude, point.Longitude);
writer.WriteRaw(rawJson);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
What am I missing?
CanConvert does not get called when you mark something with [JsonConverter]. When you use the attribute, Json.Net assumes you have provided the correct converter, so it doesn't bother with the CanConvert check. If you remove the attribute, then it will get called by virtue of you passing the converter instance to the settings. What you are seeing is Json.Net testing your converter for all the other property types.
EDIT
I put together a quick fiddle to show what I mean (code is also reproduced below for completeness).
With no changes the program, CanConvert() gets called on the FooConverter for all types except Foo, yet it still converts Foo correctly.
If you comment out the [JsonConverter] attribute on the Wrapper.Foo property, you can see that CanConvert() will now get called for type Foo by virtue of the FooConverter being included in the JsonSerializerSettings.
If you instead comment out the line in Main where the FooConverter is added to the settings, then CanConvert is never called for any type, yet Foo is still converted correctly due to the [JsonConverter] attribute applied to the Foo property in the Wrapper class.
So the takeaway here is that there are two mechanisms for indicating whether a converter should be used, and you don't need both. You can apply an attribute, and that will tell Json.Net that a particular converter should be used for a particular property (or class) and it does not need to ask the converter first. Alternatively, you can add the converter to the settings, in which case Json.Net has to ask each converter whether it can handle each type. The former is a bit more efficient, while the latter is useful in situations where you don't own the source code for the class you're trying to convert. Hope this makes sense.
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
public class Program
{
public static void Main()
{
JsonSerializerSettings settings = new JsonSerializerSettings();
// Comment out the following line and CanConvert() never gets called on
// FooConverter for any type yet the FooConverter is still working due
// to the JsonConverter attribute applied to Wrapper.Foo
settings.Converters.Add(new FooConverter());
settings.Converters.Add(new BarConverter());
settings.Formatting = Formatting.Indented;
Wrapper w = new Wrapper
{
Foo = new Foo
{
A = "bada",
B = "boom",
},
Bar = new Bar
{
C = "bada",
D = "bing"
}
};
string json = JsonConvert.SerializeObject(w, settings);
Console.WriteLine(json);
}
class Wrapper
{
// Comment out this attribute and CanConvert will be called on FooConverter
// for type Foo due to the fact that the FooConverter has been added to the
// JsonSerializerSettings
[JsonConverter(typeof(FooConverter))]
public Foo Foo { get; set; }
public Bar Bar { get; set; }
}
class Foo
{
public string A { get; set; }
public string B { get; set; }
}
class Bar
{
public string C { get; set; }
public string D { get; set; }
}
class FooConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
bool result = typeof(Foo).IsAssignableFrom(objectType);
Console.WriteLine("FooConverter CanConvert() called for type " +
objectType.Name + " (result = " + result + ")");
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var foo = (Foo) value;
JObject jo = new JObject();
jo.Add("AplusB", new JValue(foo.A + " " + foo.B));
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
class BarConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
bool result = typeof(Bar).IsAssignableFrom(objectType);
Console.WriteLine("BarConverter CanConvert() called for type " +
objectType.Name + " (result = " + result + ")");
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var bar = (Bar) value;
JObject jo = new JObject();
jo.Add("CplusD", new JValue(bar.C + " " + bar.D));
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
I'm using the official Mongo C# Driver, and RestSharp to call a Rest Api with Json.NET to perform my serialization/deserialization. Say I have a Person class as follows, which I'd like to POST & GET:
public class Person
{
[JsonProperty("_id"),JsonConverter(typeof(ObjectIdConverter))]
public ObjectId Id {get;set;}
public string Name {get;set;}
}
I create a new Person object:
var person = new Person{Id = ObjectId.GenerateId(),Name='Joe Bloggs'};
POST it, and on the server I see the following which is correct:
{ _id: 52498b56904ee108c99fbe88, name: 'Joe Bloggs'}
The problem, is when I perform a GET the ObjectId I get on the client is {0000000000000...}
i.e. not the {5249.....} I'd expect. The raw response is showing the correct value, but once I deserialize I loose it.
The ObjectIdConverter code is :
public class ObjectIdConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var objectId = (ObjectId)existingValue; // at this point existingValue is {000...}
return objectId;
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof (ObjectId));
}
}
Any help would be appreciated.
You are implementing the ReadJson method of the converter incorrectly. The existingValue parameter does not give you the deserialized value read from the JSON, it gives you the existing value of the object that you will be replacing. In most cases this will be null or empty. What you need to do is use the reader to get the value from the JSON, convert it as needed, then return the converted value.
Assuming your ObjectId class has a constructor that accepts a hex string, here is how you would implement the ReadJson method:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
return new ObjectId(token.ToObject<string>());
}
There is a piece of string I'd like to deserialize with DataContractJsonSerializer. There is a member in the JSON string that may be an object sometimes or a string other times. Like so:
{ a: 1, b: 2, c: "c" }
or
{ a: 1, b: 2, c: {c1: 3, c2: 4} }
I know the structure of the object in the second case (let's call that class Inner), but how can DataContractJsonSerializer convert an object of two possible types?
I tried making an explicit and implicit cast operator to convert from string to Inner, but it isn't getting hit. An InvalidCastException keeps on getting thrown.
I had a similar situation, where an API was returning either an array of objects, or just the single object not in an array if there was only one result. I was completely unable to get the DataContractJsonSerializer to do this. I ended up having to switch to the JSON.NET library and fiddle with JsonConverters a bit to get it working.
In your DataContract, declare 'c' as type Inner.
[DataMember(Name = "c")]
public Inner C { get; set; }
Then write a JsonConverter to examine the token type and do the right thing when the deserializer attempts to deserialize an Inner.
internal class StringOrInnerConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return objectType == typeof(Inner);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
var ser = new JsonSerializer();
if (reader.TokenType == JsonToken.StartObject) {
var inn = ser.Deserialize<Inner>(reader);
return inn;
} else if (reader.TokenType == JsonToken.String) {
var str = ser.Deserialize<string>(reader);
return (Inner)str; // Or however you want to convert string to Inner
} else {
return default(Inner);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
throw new System.NotImplementedException();
}
}