2012-09-05 9 views
17

Mam klasySerializing dziesiętny na JSON, jak zaokrąglić?

public class Money 
{ 
    public string Currency { get; set; } 
    public decimal Amount { get; set; } 
} 

i chciałbyś go do serializacji JSON. Jeśli używam JavaScriptSerializer uzyskać

{"Currency":"USD","Amount":100.31000} 

Ponieważ API muszę odpowiadać na potrzeby JSON ilościach z maksymalnie dwóch miejsc po przecinku, czuję powinno być możliwe, aby w jakiś sposób zmieniają sposób, w jaki JavaScriptSerializer serializes pole dziesiętny, ale nie mogę się dowiedzieć jak. Istnieje SimpleTypeResolver, który możesz przekazać w konstruktorze, ale działa tylko na typach, o ile mogę to zrozumieć. Numer JavaScriptConverter, który można dodać za pomocą RegisterConverters (...), wydaje się być wykonany dla Dictionary.

Chciałbym dostać

{"Currency":"USD","Amount":100.31} 

po I serializacji. Również przejście do podwójnego nie wchodzi w rachubę. I prawdopodobnie potrzebuję trochę zaokrąglić (100.311 powinno stać się 100.31).

Czy ktoś wie, jak to zrobić? Czy istnieje alternatywa dla JavaScriptSerializer, która pozwala bardziej szczegółowo sterować serializacją?

+0

Czy chcesz tylko zaokrąglić kwotę podczas serializacji? Możesz ewentualnie dodać inną właściwość do klasy i serializować ją zamiast właściwości Amount i oznaczyć oryginalną właściwość Amount, aby nie była serializowana. –

+0

@MarkSherretta Tak, chcę tylko zaokrąglić podczas serializacji do JSON. Czy mogę to zrobić bez przekształcania go w ciąg znaków ("Ilość": "100.31")? Zaokrąglone podwójne pole do serializacji? – Halvard

+0

Najlepiej, aby zmiana była tylko w jednym miejscu. W moim przypadku można to zrobić dla wszystkich liczb dziesiętnych, które są serializowane (nie tylko w tym obiekcie). – Halvard

Odpowiedz

3

W pierwszym przypadku 000 nie robi szkody, wartość nadal jest taka sama i będzie deserializowana do dokładnie tej samej wartości.

W drugim przypadku JavascriptSerializer nie pomoże. JavacriptSerializer nie powinien zmieniać danych, ponieważ przekształca je do znanego formatu, który nie zapewnia konwersji danych na poziomie użytkownika (ale zapewnia niestandardowe konwertery obiektów). To, czego chcesz, to serializacja konwersji + to zadanie dwufazowe.

dwie propozycje:

1) Za pomocą DataContractJsonSerializer: dodać kolejną właściwość, która zaokrągla wartość:

public class Money 
{ 
    public string Currency { get; set; } 

    [IgnoreDataMember] 
    public decimal Amount { get; set; } 

    [DataMember(Name = "Amount")] 
    public decimal RoundedAmount { get{ return Math.Round(Amount, 2); } } 
} 

2) klon obiektu zaokrąglania wartości:

public class Money 
{ 
    public string Currency { get; set; } 

    public decimal Amount { get; set; } 

    public Money CloneRounding() { 
     var obj = (Money)this.MemberwiseClone(); 
     obj.Amount = Math.Round(obj.Amount, 2); 
     return obj; 
    } 
} 

var roundMoney = money.CloneRounding(); 

Chyba json.net nie mogę tego zrobić, ale nie jestem w 100% pewny.

+0

W moim konkretnym przypadku '000' wyrządza szkodę, ponieważ część odbierająca (nad którą nie mam żadnej kontroli) zgłasza błąd weryfikacji na więcej niż dwóch miejscach po przecinku. Poza tym, z pewnością rozważę twoje rozwiązanie. – Halvard

+1

Dziękuję za odpowiedź :) Ustawiam ją, aby poprawiała, mimo że nie sprawdziłem sugestii klonowania (poszedłem do sugestii 'DataContractJsonSerializer' i działa dobrze). – Halvard

7

Po prostu przeszedłem przez ten sam problem, ponieważ miałem kilka cyfr po przecinku z serią 1,00, a niektóre z 1,0000. To jest moja zmiana:

Utwórz JsonTextWriter, który może zaokrąglić wartość do 4 miejsc dziesiętnych. Każdy dziesiętny zostanie zaokrąglona do 4 miejsc po przecinku: 1.0 staje 1,0000 i 1,0000000 również staje 1,0000

private class JsonTextWriterOptimized : JsonTextWriter 
{ 
    public JsonTextWriterOptimized(TextWriter textWriter) 
     : base(textWriter) 
    { 
    } 
    public override void WriteValue(decimal value) 
    { 
     // we really really really want the value to be serialized as "0.0000" not "0.00" or "0.0000"! 
     value = Math.Round(value, 4); 
     // divide first to force the appearance of 4 decimals 
     value = Math.Round((((value+0.00001M)/10000)*10000)-0.00001M, 4); 
     base.WriteValue(value); 
    } 
} 

Użyj własnego pisarz zamiast standardowego jednego:

var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create(); 
var sb = new StringBuilder(256); 
var sw = new StringWriter(sb, CultureInfo.InvariantCulture); 
using (var jsonWriter = new JsonTextWriterOptimized(sw)) 
{ 
    jsonWriter.Formatting = Formatting.None; 
    jsonSerializer.Serialize(jsonWriter, instance); 
} 
+0

Przetestowałem to rozwiązanie i to też działa. Musiałem zmienić 'var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create();' na 'var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create (nowy JsonSerializerSettings());' aby go skompilować. Być może dlatego, że jestem na platformie .Net 4.5? – Halvard

8

Na przyszłość, można to osiągnąć w Json.netto dość elegancko, tworząc zwyczaj JsonConverter

public class DecimalFormatJsonConverter : JsonConverter 
{ 
    private readonly int _numberOfDecimals; 

    public DecimalFormatJsonConverter(int numberOfDecimals) 
    { 
     _numberOfDecimals = numberOfDecimals; 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     var d = (decimal) value; 
     var rounded = Math.Round(d, _numberOfDecimals); 
     writer.WriteValue((decimal)rounded); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, 
     JsonSerializer serializer) 
    { 
     throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter."); 
    } 

    public override bool CanRead 
    { 
     get { return false; } 
    } 

    public override bool CanConvert(Type objectType) 
    { 
     return objectType == typeof(decimal); 
    } 
} 

Jeśli tworzysz serializers w kodzie za pomocą konstruktora jawnie, to będzie działać dobrze, ale myślę, że jest ładniejszy ozdobić odpowiednie właściwości z JsonConverterAttribute, w którym to przypadku klasy muszą mieć publiczny konstruktor bez parametrów. Rozwiązałem to, tworząc podklas, która jest specyficzna dla formatu, który chcę.

public class SomePropertyDecimalFormatConverter : DecimalFormatJsonConverter 
{ 
    public SomePropertyDecimalFormatConverter() : base(3) 
    { 
    } 
} 

public class Poco 
{ 
    [JsonConverter(typeof(SomePropertyDecimalFormatConverter))] 
    public decimal SomeProperty { get;set; } 
} 

Konwerter niestandardowy pochodzi z Json.NET documentation.

+0

Czy jest coś jeszcze, co musisz zrobić, aby to aktywować? Dodałem klasę i atrybut, ale nigdy nie wchodzę w funkcję WriteJson. – NickG

+0

@NickG to nie powinno. Właśnie potwierdziłem nową aplikacją konsolową i najnowszą Json.NET. Dodałem 2 klasy konwerterów i Poco, jak zdefiniowano tutaj, oraz 'JsonConvert.SerializeObject (nowe Poco {SomeProperty = 1.23456789m});' powoduje serializowanie wartości '{" SomeProperty ": 1.235}' – htuomola

+0

Nie rozumiałem, jak to powinno wyglądać działa - wydaje się działać tylko podczas tworzenia JSON-a nie podczas analizowania ... Miałem nadzieję, że zamieniłoby to dziwne wartości w JSON dostawcy na bardziej sensowne. Np. 57.400000000000000001 itd ... Jednak teraz zrobiłem to w logicznym kodzie biz. – NickG

12

Nie byłem w pełni usatysfakcjonowany wszystkimi dotychczasowymi technikami, aby to osiągnąć. JsonConverterAttribute wydawał się najbardziej obiecujący, ale nie mogłem żyć z mocno zakodowanymi parametrami i proliferacją klas konwerterów dla każdej kombinacji opcji.

Tak, przesłałem PR, który dodaje możliwość przekazywania różnych argumentów do JsonConverter i JsonProperty. To zostało przyjęte w górę i spodziewam się będzie w następnej wersji (co jest następny po 6.0.5)

Następnie można zrobić to tak:

public class Measurements 
{ 
    [JsonProperty(ItemConverterType = typeof(RoundingJsonConverter))] 
    public List<double> Positions { get; set; } 

    [JsonProperty(ItemConverterType = typeof(RoundingJsonConverter), ItemConverterParameters = new object[] { 0, MidpointRounding.ToEven })] 
    public List<double> Loads { get; set; } 

    [JsonConverter(typeof(RoundingJsonConverter), 4)] 
    public double Gain { get; set; } 
} 

Patrz testu CustomDoubleRounding() dla przykładu.

+0

Nie mogę tego zrobić w wersji 8 .. Czy istnieje pełny przykład tego, co jest wymagane, aby to działało? Kod działa poprawnie, ale niczego nie zaokrągla. – NickG

+0

Och, właśnie zdałem sobie sprawę, że to nie działa podczas deserializacji - tylko podczas tworzenia serializacji do nowego JSON :( – NickG

+0

Spróbuj zmienić CanRead, aby zwrócić true, a następnie zaimplementować właściwy ReadJson. Nie ma powodu, dla którego mógłbym myśleć, że nie można tego łatwo zrobić dwukierunkowe – BrandonLWhite