2012-09-05 13 views
17

Ho una classeSerializzare un decimale in JSON, come arrotondare?

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

e vorrebbe serializzare a JSON. Se uso il JavaScriptSerializer ottengo

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

A causa della API devo conformarsi alle esigenze JSON ammonta con un massimo di due cifre decimali, ritengo che dovrebbe essere possibile modificare in qualche modo il modo in cui il JavaScriptSerializer serializza un campo decimale, ma non riesco a scoprire come. C'è lo SimpleTypeResolver che puoi passare nel costruttore, ma funziona solo sui tipi per quanto posso capire. È possibile creare JavaScriptConverter, che è possibile aggiungere tramite RegisterConverters (...) per Dictionary.

vorrei ottenere

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

dopo ho serializzare. Inoltre, il passaggio al doppio è fuori questione. E probabilmente ho bisogno di fare qualche arrotondamento (100.311 dovrebbe diventare 100.31).

Qualcuno sa come fare? C'è forse un'alternativa allo JavaScriptSerializer che ti permette di controllare la serializzazione in modo più dettagliato?

+0

Si desidera arrotondare l'importo solo durante la serializzazione? È possibile aggiungere un'altra proprietà alla classe e serializzarla anziché la proprietà Amount e contrassegnare la proprietà Amount originale per non essere serializzata. –

+0

@MarkSherretta Sì, voglio solo arrotondare durante la serializzazione su JSON. Posso farlo senza trasformarlo in una stringa ("Amount": "100.31")? Un doppio campo arrotondato per la serializzazione? – Halvard

+0

Preferibilmente il cambiamento dovrebbe essere in un solo posto. Nel mio caso è OK farlo per tutti i decimali che vengono serializzati (non solo in questo oggetto). – Halvard

risposta

3

Nel primo caso il 000 non danneggia, il valore è lo stesso e verrà deserializzato allo stesso valore.

Nel secondo caso JavascriptSerializer non ti aiuterà. Lo JavacriptSerializer non deve modificare i dati, poiché lo serializza in un formato noto non fornisce la conversione dei dati a livello di membro (ma fornisce convertitori oggetto personalizzati). Quello che vuoi è una conversione + serializzazione, questo è un compito a due fasi.

due suggerimenti:

1) Utilizzare DataContractJsonSerializer: aggiungere un'altra proprietà che arrotonda il valore:

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) Clonare l'oggetto di arrotondamento dei valori:

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(); 

immagino json.net non posso farlo neanche io, ma non ne sono sicuro al 100%.

+0

Nel mio caso specifico "000" fa male poiché la parte ricevente (su cui non ho alcun controllo) lancia un errore di convalida su più di due decimali. A parte questo, prenderò certamente in considerazione la tua soluzione. – Halvard

+1

Grazie per la tua risposta :) L'ho impostato per correggere anche se non ho controllato il suggerimento di clonazione (sono andato per il suggerimento 'DataContractJsonSerializer', e funziona bene). – Halvard

7

Ho appena avuto lo stesso problema dato che alcuni decimali sono stati serializzati con 1.00 e alcuni con 1.0000. Questa è la mia modifica:

Creare un JsonTextWriter che può arrotondare il valore a 4 decimali. Ogni decimale sarà poi arrotondato a 4 decimali: 1.0 diventa 1.0000 e 1,0 milioni diventa anche 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); 
    } 
} 

Utilizzare il proprio scrittore, invece di quella standard:

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

Ho provato questa soluzione e funziona anche. Ho dovuto cambiare 'var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create();' a 'var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create (new JsonSerializerSettings());' per farlo compilare. Forse è perché sono su. Net Framework 4.5? – Halvard

8

Per riferimento futuro, questo può essere realizzato in Json.net abbastanza elegantemente con la creazione di un custom 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); 
    } 
} 

Se si sta creando serializzatori nel codice utilizzando il costruttore in modo esplicito, questo funzionerà bene, ma penso che sia più bello per decorare le proprietà rilevanti con JsonConverterAttribute, nel qual caso la classe deve avere un costruttore pubblico, senza parametri. Ho risolto questo creando una sottoclasse che è specifica per il formato che voglio.

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

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

Il convertitore personalizzato è stato derivato da Json.NET documentation.

+0

C'è altro che devi fare per attivare questo? Ho aggiunto la classe e l'attributo, ma non entra mai nella funzione WriteJson. – NickG

+0

@NickG non dovrebbe. Ho appena confermato con la nuova app per console e l'ultimo Json.NET. Ho aggiunto le 2 classi del convertitore e il Poco come definito qui e 'JsonConvert.SerializeObject (new Poco {SomeProperty = 1.23456789m});' restituisce il valore serializzato di '{" SomeProperty ": 1.235}' – htuomola

+0

Mi sbagliava il modo in cui questo dovrebbe lavoro - sembra funzionare solo quando si crea JSON - non quando lo si analizza ... Speravo che convertisse i valori strani nel JSON del fornitore in quelli più sensati. Es. 57.400000000000000001 ecc ... Tuttavia ora ho fatto questo in codice logico biz. – NickG

12

Non ero completamente soddisfatto di tutte le tecniche finora necessarie per raggiungere questo obiettivo. JsonConverterAttribute sembrava il più promettente, ma non potevo vivere con parametri hard-coded e proliferazione di classi di convertitori per ogni combinazione di opzioni.

Quindi, ho inviato uno PR che aggiunge la possibilità di passare vari argomenti a JsonConverter e JsonProperty. E 'stato accettato a monte e mi aspetto che sarà nella prossima release (qualunque sia il prossimo dopo la 6.0.5)

È quindi possibile fare in questo modo:

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; } 
} 

Fare riferimento alla prova CustomDoubleRounding() per un esempio.

+0

Non riesco a farlo funzionare nella versione 8 .. Esiste un esempio completo di cosa è necessario per fare in modo che funzioni? Il codice funziona bene ma non arrotonda nulla. – NickG

+0

Oh, ho appena realizzato che non funziona durante la deserializzazione - solo durante la creazione di serializzazione su nuovo JSON :( – NickG

+0

Provare a cambiare CanRead per restituire true e quindi implementare un ReadJson corretto. Non c'è motivo per cui possa pensare che questo non possa essere fatto facilmente bidirezionale: – BrandonLWhite