Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading huge integers with Json.NET

Tags:

c#

json.net

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.

like image 463
bkjvbx Avatar asked Oct 12 '15 08:10

bkjvbx


2 Answers

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
like image 140
Brian Rogers Avatar answered Oct 18 '22 20:10

Brian Rogers


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 ...
    }
}
like image 1
dlght Avatar answered Oct 18 '22 22:10

dlght