Convert long number as string in the serialization
Asked Answered
C

6

22

I have a custom made class that use a long as ID. However, when I call my action using ajax, my ID is truncated and it loses the last 2 numbers because javascript loses precision when dealing with large numbers. My solution would be to give a string to my javascript, but the ID have to stay as a long on the server side.

Is there a way to serialize the property as a string? I'm looking for some kind of attribute.

Controller

public class CustomersController : ApiController
{
   public IEnumerable<CustomerEntity> Get()
   {
      yield return new CustomerEntity() { ID = 1306270928525862486, Name = "Test" };
   }
}

Model

public class CustomerEntity
{
   public long ID { get; set; }
   public string Name { get; set; }
}

JSON Result

[{"Name":"Test","ID":1306270928525862400}]
Cadmarr answered 28/6, 2013 at 16:19 Comment(0)
I
31

You could probably create a custom JsonConverter and apply it on your property.

Following is an example (NOTE: I haven't used this api before so it could probably be improved more, but following should give you a rough idea):

public class Person
{
    [JsonConverter(typeof(IdToStringConverter))]
    public long ID { get; set; }

    public string Name { get; set; }
}

public class IdToStringConverter : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken jt = JValue.ReadFrom(reader);

        return jt.Value<long>();
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(System.Int64).Equals(objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value.ToString());
    }
}

Web API Action:

public Person Post([FromBody]Person person)
{
    return person;
}

Request:

POST http://asdfasdf/api/values HTTP/1.1  
Host: servername:9095  
Connection: Keep-Alive  
Content-Type: application/json  
Content-Length: 42  

{"ID":"1306270928525862400","Name":"Mike"}

Response:

HTTP/1.1 200 OK  
Content-Length: 42  
Content-Type: application/json; charset=utf-8  
Server: Microsoft-HTTPAPI/2.0  
Date: Fri, 28 Jun 2013 17:02:18 GMT  

{"ID":"1306270928525862400","Name":"Mike"}

EDIT:
if you do not want to decorate the property with an attribute, you could instead add it to the Converters collection. Example:

config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new IdToStringConverter());
Insistence answered 28/6, 2013 at 17:15 Comment(7)
Thanks, this is great! The only downside of that answer is that our entities are inherited from a custom entity template in our ORM that is used in every project that we have (some of them are not web based). That would imply to add a dependency from Newtonsoft.JSON to the ORM, and every project that we have would need newtonsoft too. I would prefer an attribute that doesn't depend on a DLL.Cadmarr
@Bruno: one thing to warn you though. This converter would convert all longs to string. You should probably look into ways to do some checks so that it doesn't apply application wide and also any performance enhancements as to not trigger this converter apart from types you expect, like example: CustomerEntityInsistence
We rarely use long for something that is not an ID anywayCadmarr
Nice solution - it might be worth mentioning that it doesn't handle long? (Nullable<long>) which sometimes is useful as well.Osman
Also regarding the 'Edit' - in case someone has the same problem - it didn't want to work for me because of #24620610Osman
value.ToString() can be localized when value is a long, so you should format the value using invariant settings, e.g. ((IConvertible)value).ToString(NumberFormatInfo.InvariantInfo)Icarus
Thanks, It works. I want to convert String to Number type gist.github.com/najathi/23b7bbba45583f357e5659a5e946d031Judgeship
P
2

How about a view model that only has string properties, like this:

public class CustomerEntityViewModel
{
    public string ID { get; set; }
    public string Name { get; set; }
}

Now you are only dealing with strings and JSON serialization problems go away.

Penhall answered 28/6, 2013 at 16:27 Comment(6)
I think he wants to keep long on server sideBander
@Bander - He can, because he could convert the CustomerEntity model class to the CustomerEntityViewModel and pass that down to the client, then when returning to the server it could be converted back to a CustomerEntity class and the ID is a long again. I agree it is jumping through hoops, but if that precision is necessary, then some conversion to string needs to happen somewhere.Penhall
That would imply to make a view model for each of my entities (because they all use a long ID) and duplicate every properties in each of themCadmarr
@Cadmarr - Sadly, yes that is what I am implying. :-(Penhall
@Cadmarr Perhaps you could refactor your entities and all inherit from a base class that contains the ID, that way if you ever need to make a sweeping change like this again you will only need to change it once.Pulse
The problem with this approach is (in some cases, not talking specifically about this case) that the default value of string is different than int/long/etc'. If these models are being used to make requests to an endpoint that expects a number or zero, but then gets an empty string instead, it might fail or present erratic behavior. This is solvable as well, however it's safer to just create a JSON converter at one place and solve the issuePoolroom
T
2

I would probably just create an anonymous type when I call the json serializer like;

 JsonConvert.SerializeObject(new { instance.Name, instance.ID.ToString() } );

In the case where your class has 20 or something fields and this becomes a very ugly solution I would add a string to the class called ID, change the long to lID or something and use the serializer settings to ignore the long when serializing so the resulting json will only have the string version.

There are several different attributes that will accomplish this for example;

 public class CustomerEntity
 {
      [ScriptIgnore]
      public long _ID { get; set; }
      public string ID { get; set; }
      public string Name { get; set; }
 }

Will not serialize _ID

Tipstaff answered 28/6, 2013 at 16:28 Comment(0)
T
2

Depending on your case, you could potentially use getters and setters to masquerade the property as a string during JSON serialization.

public class Money
{
    [JsonIgnore]
    public decimal Money { get; set; }

    [JsonProperty("money")]
    public string MoneyAsString
    {
        get { return Money.ToString("0.00"); }
        set { Money = decimal.Parse(value); }
    }
}
Tevet answered 31/3, 2017 at 18:22 Comment(0)
M
1

In new System.Text.Json class.

You can use this:


    using System;
    using System.Text.Json;
    using System.Text.Json.Serialization;

    namespace XXX.Common
    {
        /// <summary>
        /// 
        /// </summary>
        public class LongToStringConverter : JsonConverter<long>
        {
            public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
            {
                return Convert.ToInt64(reader.GetString());
            }

            public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
            {
                writer.WriteStringValue(value.ToString());
            }
        }

    }

You can use it in Startup.cs

services.AddControllers().AddJsonOptions(options =>
{    
    options.JsonSerializerOptions.Converters.Add(new LongToStringConverter());
});

For more info,view How to write custom converters for JSON serialization (marshalling) in .NET

Motta answered 6/2 at 3:10 Comment(0)
C
0

Maybe a viable workaround would be to use another type for the ID? You could use a GUID for example. With a GUID you will never run out of bounds and also this will get serialized as a string by default I think.

Contributory answered 28/6, 2013 at 16:31 Comment(1)
I can't change the type, but I'm looking for a way to serialized a long as a string like the GUIDCadmarr

© 2022 - 2024 — McMap. All rights reserved.