Reading huge integers with Json.NET - c#

I've got some json with huge integers, in the order of a few hundred digits. I'd like to parse those as BouncyCastle's BigInteger (https://github.com/onovotny/BouncyCastle-PCL/blob/pcl/crypto/src/math/BigInteger.cs).
{
"bigNumber":12093812947635091350945141034598534526723049126743245...
}
So I've implemented a converter, using a contract resolver in the default settings.
internal class BigIntegerConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken jToken = JToken.Load(reader);
return new BigInteger(jToken.ToString());
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(BigInteger));
}
}
public class BigIntegerContractResolver : DefaultContractResolver
{
private static readonly JsonConverter bigIntegerConverter = new BigIntegerConverter();
private static Type type = typeof(BigInteger);
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (objectType == type)
{
return bigIntegerConverter;
}
return base.ResolveContractConverter(objectType);
}
}
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
ContractResolver = new BigIntegerContractResolver()
};
The writer works as it should, writing a (large) integer value instead of the class BigInteger with all its properties etc. However, the reading fails. Neither ReadJson nor CanConvert appear to be invoked.
I get the following exception:
JsonReaderException: JSON integer 340597435091750914358634185762341897561435984635897436598435643875643189576413589743659817456... is too large or small for an Int64.
How do I get Json.NET to parse this number as a string instead of an integer?
Ideally I don't want to have to parse the json string myself first, to add quotes.

If your large number isn't quoted, Json.Net will deserialize it as a System.Numerics.BigInteger. This happens inside the JsonTextReader, well before the converter gets a chance to handle it. So if you want your result type to be Org.BouncyCastle.Math.BigInteger, you'll need to convert from System.Numerics.BigInteger. (Seems a little backwards, I know. The other alternative is to create your own JsonTextReader, but that is probably going to be more trouble than it is worth -- most of the useful bits of the existing reader are in private or internal methods, so subclassing it is not practical.)
I was able to get this converter to work:
class BigIntegerConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Org.BouncyCastle.Math.BigInteger));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
System.Numerics.BigInteger big = (System.Numerics.BigInteger)reader.Value;
return new Org.BouncyCastle.Math.BigInteger(big.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(value.ToString());
}
}
Here is the test program I used. Note that I did not use a resolver. JsonSerializerSettings has a Converters collection, so I just added the BigIntegerConverter to that.
class Program
{
static void Main(string[] args)
{
string json = #"
{
""bigNumber"": 12093812947635091350945141034598534526723049126743245
}";
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new BigIntegerConverter() }
};
Foo foo = JsonConvert.DeserializeObject<Foo>(json);
Console.WriteLine(foo.BigNumber.ToString());
}
}
class Foo
{
public Org.BouncyCastle.Math.BigInteger BigNumber { get; set; }
}
Output:
12093812947635091350945141034598534526723049126743245

You can try creating object that handles the output of the json like this:
public class YourModel
{
[JsonConverter(typeof(CustomConverter<BigInteger>))]
public BigInteger YourProperty{ get; set; }
}
And now it can become more generic for every type you need:
public class CustomConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
your code ..
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//explicitly specify the concrete type we want to create
return serializer.Deserialize<T>(reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
your code ...
}
}

Related

C# Newtonsoft.Json Custom Deserializer

I'm working with an API that is returning results to me in a different way than I'm used to dealing with, and seemingly non-standard.
For example, here's a snippet of Customer data:
{
"CustomerID": {
"value": "EXAMPLE"
},
"CustomerCurrencyID": {
"value": "USD"
}
}
That "value" property seems very unnecessary, so I would like to see if I can just bypass that all together and deserialize that JSON into an object like so:
class Customer {
public string CustomerID { get; set; }
public string CustomerCurrencyID { get; set; }
}
I'm currently working on writing a custom JsonConverter to handle this, so if I'm heading down the right path just let me know, but any tips/tricks here would be much appreciated!
You can do this with a generic custom JsonConverter such as the following:
public class WrapWithValueConverter<TValue> : JsonConverter
{
// Here we take advantage of the fact that a converter applied to a property has highest precedence to avoid an infinite recursion.
class DTO { [JsonConverter(typeof(NoConverter))] public TValue value { get; set; } public object GetValue() => value; }
public override bool CanConvert(Type objectType) => typeof(TValue).IsAssignableFrom(objectType);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=> serializer.Serialize(writer, new DTO { value = (TValue)value });
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> serializer.Deserialize<DTO>(reader)?.GetValue();
}
public class NoConverter : JsonConverter
{
// NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
// By https://stackoverflow.com/users/3744182/dbc
// To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter
public override bool CanConvert(Type objectType) { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }
public override bool CanRead => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
Then you can apply it to your model as follows:
class Customer {
[JsonConverter(typeof(WrapWithValueConverter<string>))]
public string CustomerID { get; set; }
[JsonConverter(typeof(WrapWithValueConverter<string>))]
public string CustomerCurrencyID { get; set; }
}
Demo fiddle #1 here.
Or, if you want all strings to be wrapped in a {"value": <string value>} object, you can add the converter to JsonSerializerSettings.Converters when serializing and deserializing:
var settings = new JsonSerializerSettings
{
Converters = { new WrapWithValueConverter<string>() },
};
var model = JsonConvert.DeserializeObject<Customer>(json, settings);
var json2 = JsonConvert.SerializeObject(model, Formatting.Indented, settings);
Demo fiddle #2 here.
If your value is an enum and you want to serialize it as a string, you can replace NoConverter with StringEnumConverter by using the following:
public class WrapEnumWithValueConverter<TEnum> : JsonConverter where TEnum: Enum
{
// Here we take advantage of the fact that a converter applied to a property has highest precedence to avoid an infinite recursion.
class DTO { [JsonConverter(typeof(StringEnumConverter))] public TEnum value { get; set; } public object GetValue() => value; }
public override bool CanConvert(Type objectType) => typeof(TEnum).IsAssignableFrom(objectType);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=> serializer.Serialize(writer, new DTO { value = (TEnum)value });
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> serializer.Deserialize<DTO>(reader)?.GetValue();
}
Demo fiddle #3 here.

Reading sub-objects from json as strings with Json.NET (C#) [duplicate]

This question already has an answer here:
Efficiently get full json string in JsonConverter.ReadJson()
(1 answer)
Closed 2 years ago.
I'm reading a large object which contains thousands of sub-objects that I don't want to deserialize into full C# objects (for performance reasons) but just load them as dummy wrapper objects to keep their JSON as strings (for later phase where they are serialized again).
So far I have the following concept code (not final), but I would prefer to avoid the JObject creation and just read somehow the sub-objects string straight from the reader into the wrapper json member string.
Is there an easy way to do so without switching over all possible tokens and reading them one by one?
namespace Playground
{
[JsonConverter(typeof(ObjectJsonWrapperConverter))]
public class ObjectJsonWrapper
{
public string Json;
}
public class ObjectJsonWrapperConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(((ObjectJsonWrapper)value).Json);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var wrapper=new ObjectJsonWrapper();
if (reader.TokenType == JsonToken.Null)
{
return null;
}
else if (reader.TokenType == JsonToken.StartObject)
{
var obj=JsonConvert.SerializeObject(JObject.Load(reader));
wrapper.Json = obj;
return wrapper;
}
else
{
throw new ArgumentException("Bad parsing");
}
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
}
Already saw the other question but it doesn't answer my needs
If I understand you correctly, what you are looking for is persisting content of JsonReader as string, and that's what JRaw does.
class ObjectJsonWrapperConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => true;
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
// This creates a raw JSON string without parsing it.
var raw = JRaw.Create(reader);
var obj = new ObjectJsonWrapper();
obj.Json = (string)raw.Value;
return obj;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use it:
public class MyObject
{
public ObjectJsonWrapper LargeObject { get; set; }
}
[JsonConverter(typeof(ObjectJsonWrapperConverter))]
public class ObjectJsonWrapper
{
public string Json { get; set; }
}
var json = #"
{
""largeObject"": {
""value"": ""some value""
}
}
";
var obj = JsonConvert.DeserializeObject<MyObject>(json);

StackOverflowException when deserializing a hierarchy of immutable polymorphic types with Newtonsoft JSON [duplicate]

EDIT: Clarify question:
I have overridden the JsonConverter for a base type (by applying [JsonConverter(typeof(TConverter))] to the superclass), but when deserializing the sub-type directly I want to use STANDARD serialization (i.e. no custom converter) for deserializing my derived object. How do I specify STANDARD serialization for use in the deserialize method, as if I had NOT overridden the JsonConverter?
I am using elastic search and can't call JsonConvert.DeserializeObject with my custom implementation of JsonConverter, and have to rely on the attribute for Elastic to use my converter.
However, using this converter as attribute seems to affect all sub classes as well, but I just want them to use the standard converter, so that I don't have to implement JsonConverter for each of many implementations.
This is my classes/logic as I would like it to look:
[Route("test")]
[HttpPost]
public HttpResponseMessage Test([FromBody] JToken json)
{
var res = json.ToObject<Product>(); // I want an object of ProductImpl type here
return Request.CreateResponse(res);
}
[JsonConverter(typeof(JsonProductConverted))]
public abstract class Product
{
}
public class ProductImpl : Product
{
}
public class JsonProductConverted : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject json = JObject.Load(reader);
//var type = GetTypeFromId((int) json["typeId"]); // Construct type from field in
var type = typeof(ProductImpl);
// var res = JsonConvert.DeserializeObject(json.ToString(), type, DEFAULT_JSONCONVERTER_HERE);
var res = DeserializeToObjectWithStandardJsonConverter(json, type);
return res;
}
public override bool CanConvert(Type objectType)
{
return false;
}
}
If I don't supply the default JsonConverter, or similar it will just use the JsonProductConverted converter, which creates an infinite loop.
Since you have added [JsonConverter(typeof(JsonProductConverted))] directly to your Product type, you could add a dummy converter to ProductImpl that returns false from CanRead and CanWrite:
[JsonConverter(typeof(NoConverter))]
public class ProductImpl : Product
{
}
public class NoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return false;
}
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This overrides the base class's converter and then falls back on default serialization for both reading and writing
Sample .Net fiddle.
Another option would be to use serializer.Populate(). This avoids the call to the converter for the object itself:
public class JsonProductConverted : JsonTypeInferringConverterBase
{
protected override Type InferType(Type objectType, JObject json)
{
//var type = GetTypeFromId((int) json["typeId"]); // Construct type from field in
return typeof(ProductImpl);
}
public override bool CanConvert(Type objectType)
{
return false;
}
}
public abstract class JsonTypeInferringConverterBase : JsonConverter
{
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
protected abstract Type InferType(Type objectType, JObject json);
protected virtual object CreateObject(Type actualType, JsonSerializer serializer, JObject json)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(actualType);
return contract.DefaultCreator();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var json = JObject.Load(reader);
var actualType = InferType(objectType, json);
// Construct object (or reuse existingValue if compatible)
if (existingValue == null || !actualType.IsAssignableFrom(existingValue.GetType()))
{
existingValue = CreateObject(actualType, serializer, json);
}
// Populate object.
using (var subReader = json.CreateReader())
{
serializer.Populate(subReader, existingValue);
}
return existingValue;
}
}
Note that the concrete objects must have parameterless constructors for this to work. If not, you can override protected virtual object CreateObject(Type actualType, JsonSerializer serializer, JObject json) and manually invoke a parameterized constructor by deserializing select properties inside the JObject json.
Sample fiddle #2.

Single Value to Collection json converter with generic initialization

Some time ago I needed a custom generic json-converter which automatically would convert object to collection if needed. My solution was:
public class SingleValueCollectionConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return reader.TokenType == JsonToken.StartArray ? serializer.Deserialize(reader, objectType) : new List<T> { (T)serializer.Deserialize(reader, typeof(T)) };
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
It worked fine until I've got the situation when I needed to use it for the property with generic type. I've got an error for using it like this:
[JsonConverter(typeof(SingleValueCollectionConverter<TData>))]
public List<TData> Data { get; set; }
It turned out that generic parameters cannot appear in attribute declarations. I found this pull-request to newtonsoftJsonConverter - https://github.com/JamesNK/Newtonsoft.Json/issues/1332 where the decision was suggested but it was not approved by the author.
So, I created non-generic converter which works universally like generic with the help of reflection:
public class SingleValueCollectionConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
foreach (var prop in objectType.GetProperties())
{
var type = prop.PropertyType;
if (!type.IsClass)
continue;
var destination = Activator.CreateInstance(objectType);
var result = reader.TokenType == JsonToken.StartArray
? serializer.Deserialize(reader, objectType)
: new List<object> { serializer.Deserialize(reader, type) };
return Mapper.Map(result, destination, result.GetType(), destination.GetType());
}
return null;
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
Using like this:
[JsonConverter(typeof(SingleValueCollectionConverter))]
public List<TData> Data { get; set; }
It works fine, but maybe somebody has better decision. I'll be appreciated for any suggestions.

Serialize root-level type differently than lower-level type

Is it possible in Json.NET to serialize a root-level type in the serialization hierarchy differently than a reference encountered at a lower level in the hierarchy?
For example, with this type
class Serialized {
public Serialized Serialized;
public int A;
}
in this setup
var serialized = new Serialized() { A = 1 };
var serialized2 = new Serialized() { A = 2 };
serialized.Serialized = serialized2;
string json = GetJson(serialized);
where json is
{
"A":1
"Serialized": {
"ref":2
}
}
Specifically, the root-level serialization should use the default serialization strategy and the lower-level ones should use a custom converter (or similar).
You need to define custom JsonConverter and decorate the Serialized property with it:
public class PropertyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
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)
{
if (value == null) return;
var serialized = (Serialized)value;
writer.WriteStartObject();
writer.WritePropertyName("ref");
writer.WriteValue(serialized.A);
writer.WriteEndObject();
}
}
And your class:
class Serialized {
[JsonConverter(typeof(PropertyConverter))
public Serialized Serialized;
public int A;
}
This will work only for one level of nesting. If you need it to work of multiple nested levels, you need to modify the converter:
public class PropertyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
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)
{
if (value == null) return;
var serialized = (Serialized)value;
writer.WriteStartObject();
writer.WritePropertyName("ref");
writer.WriteValue(serialized.A);
if(serialized.Serialized != null)
{
writer.WritePropertyName("nested");
writer.WriteValue(JsonConvert.SerializeObject(serialized.Serialized, new JsonConverter[] {new PropertyConverter()}));
}
writer.WriteEndObject();
}
}

Categories

Resources