I've searched the Jackson docs, but can't find any good documentation for the pattern of #JsonFormat for floating point numbers.
Given a field
#JsonProperty("Cost")
private Double cost;
How can I get Jackson to format it as fixed point number with four digits precision in decimal format with #JsonFormat?
PS: I know one should not use floats for money. Spare us the discussion, please.
You would need to create a custom Serializer for that. Something like
#JsonProperty("amountOfMoney")
#JsonSerialize(using = MySerializer.class)
private Double cost;
public class MySerializerextends JsonSerializer<Double> {
#Override
public void serialize(Double value, JsonGenerator generator, SerializerProvider provider) throws IOException,
JsonProcessingException {
double roundedValue = value*10000;
roundedValue = Math.round(roundedValue );
roundedValue = roundedValue /10000;
generator.writeNumber(roundedValue );
}
}
You can see about the class here
https://fasterxml.github.io/jackson-databind/javadoc/2.3.0/com/fasterxml/jackson/databind/JsonSerializer.html
The rounding part might not be the best. You can do it as you prefer ;) Using decimal format can work too. If you use writeNumber it will print the value as a number in the result Json. That's why I changed my answer from writeString and using decimal format.
You should be able to use pattern of #JsonFormat for that if the implementation allows it.
Datatype-specific additional piece of configuration that may be used
to further refine formatting aspects. This may, for example, determine
low-level format String used for Date serialization; however, exact
use is determined by specific JsonSerializer
But with jackson I believe it works only for dates.
You can specify your own formatter in custom serializer class.
formatter = new DecimalFormat();
formatter.setMaximumFractionDigits(2);
formatter.setMinimumFractionDigits(2);
formatter.setGroupingUsed(false);
DecimalFormatSymbols sym = DecimalFormatSymbols.getInstance();
sym.setDecimalSeparator('.');
formatter.setDecimalFormatSymbols(sym);
Then, in actual serialize method:
final String output = formatter.format(value);
jsonGenerator.writeNumber(output);
Building on #Veselin's answers I'm using
public class DoubleDecimalSerializerWithSixDigitPrecisionAndDotSeparator
extends JsonSerializer<Double> {
#Override
public void serialize(Double value, JsonGenerator generator, SerializerProvider serializers)
throws IOException {
generator.writeNumber(String.format(Locale.US, "%.6f", value));
}
}
The use case is the generation of CSVs in Germany, so I don't care for JSON formatting and want a "." as a decimal separator.
Related
I have a POST API like so:
#JsonDeserialize(using = MyCustomDeserializer.class)
#JsonSerialize(using = MyCustomSerializer.class)
#NotNull(message = "fromDate should not be blank!")
#FutureOrPresent(message = "fromDate must be of Future or Present")
private Instant fromDate;
I have custom serializer and deserializer like so in my utility package:
public class MyCustomSerializer extends JsonSerializer<Instant> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneOffset.UTC);
#Override
public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
String str = fmt.format(value);
gen.writeString(str);
}
}
public class MyCustomDeserializer extends JsonDeserializer<Instant> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneOffset.UTC);
#Override
public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
LocalDate date = LocalDate.parse(p.getText());
return Instant.from(fmt.parse(date.toString()));
}
}
I want to save internally with time component as start of day. The API should be taking in and outputting by only date
I tried converting this to Instant. Can someone assist how do I do this(store in db as date+start_of_day) and expose/consume as yyyy-MM-dd?
TL;DR
return date.atStartOfDay(ZoneOffset.UTC).toInstant();
(Not tested, please forgive any typos.)
Details
You are really trying to fit a square peg into a round hole. An Instant is a point in time, and at that point in time, it is two or three different dates in different time zones. Settling for UTC helps, then we can get it to work. It still isn’t ideal. The standard solution (hack) to the discrepancy is to represent the date as the start of the day in some time zone, and UTC is an excellent choice.
Why your code didn’t work
Parsing the date (after formatting it) in your deserializer gives the date and the UTC offset, but no time of day. We need all three to establish an unambiguous point in time. Instead in my code I explicitly specify the start of the day.
I got an Android app which receives Json responses from a web service. One of the responses is a json string with a date inside. I get the date in the form of a number like "1476399300000". When I try to create an object with it using GSON I get this error:
Failed to parse date ["1476399300000']: Invalid time zone indicator '0' (at offset 0)
Both sides are working with java.util.Date
How can I fix this issue?
The value 1476399300000 looks like ms from the Unix epoch beginning. Just add a type adapter to your Gson:
final class UnixEpochDateTypeAdapter
extends TypeAdapter<Date> {
private static final TypeAdapter<Date> unixEpochDateTypeAdapter = new UnixEpochDateTypeAdapter();
private UnixEpochDateTypeAdapter() {
}
static TypeAdapter<Date> getUnixEpochDateTypeAdapter() {
return unixEpochDateTypeAdapter;
}
#Override
public Date read(final JsonReader in)
throws IOException {
// this is where the conversion is performed
return new Date(in.nextLong());
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final Date value)
throws IOException {
// write back if necessary or throw UnsupportedOperationException
out.value(value.getTime());
}
}
Configure your Gson instance:
final Gson gson = new GsonBuilder()
.registerTypeAdapter(Date.class, getUnixEpochDateTypeAdapter())
.create();
Gson instances are thread-safe as well as UnixEpochDateTypeAdapter is, and can exist as one instance globally. Example:
final class Mapping {
final Date date = null;
}
final String json = "{\"date\":1476399300000}";
final Mapping mapping = gson.fromJson(json, Mapping.class);
System.out.println(mapping.date);
System.out.println(gson.toJson(mapping));
would give the following output:
Fri Oct 14 01:55:00 EEST 2016
{"date":1476399300000}
Note that the type adapter is configured to override the default Gson date type adapter. So you might need to use a more complicated analysis to detect whether is just ms of the Unix epoch. Also note, that you could use JsonDeserializer, but the latter works in JSON-tree manner whilst type adapters work in the streaming way that's somewhat more efficient probably not accumulating intermediate results.
Edit:
Also, it may look confusing, but Gson can make value conversions for primitives. Despite your payload has a string value, JsonReader.nextLong() can read a string primitive as a long value. So the UnixEpochDateTypeAdapter.write should be out.value(String.valueOf(value.getTime())); in order not to modify JSON literals.
Edit
There's also a shorter solution (working with JSON in-memory trees rather than data streaming) which is simply:
final Gson builder = new GsonBuilder()
.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
public Date deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException {
return new Date(jsonElement.getAsJsonPrimitive().getAsLong());
}
})
.create();
I am using BigDecimal to get some price values. Requirement is something like this, what ever the value we fetch from database, the displayed valued should have 2 decimal points.
Eg:
fetched value is 1 - should be displayed as 1.00
fetched value is 1.7823 - should be displayed as 1.78
I am using setScale(2, BigDecimal.ROUND_HALF_UP) but still some places, if the data from DB is a whole number then the same is being displayed !!
I mean if the value is 0 from DB its displayed as 0 only. I want that to be displayed as 0.00
Thanks
BigDecimal is immutable, any operation on it including setScale(2, BigDecimal.ROUND_HALF_UP) produces a new BigDecimal. Correct code should be
BigDecimal bd = new BigDecimal(1);
bd.setScale(2, BigDecimal.ROUND_HALF_UP); // this does change bd
bd = bd.setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println(bd);
output
1.00
Note - Since Java 9 BigDecimal.ROUND_HALF_UP has been deprecated and you should now use RoundingMode.ROUND_HALF_UP.
you can use the round up format
BigDecimal bd = new BigDecimal(2.22222);
System.out.println(bd.setScale(2,BigDecimal.ROUND_UP));
Hope this help you.
To format numbers in JAVA you can use:
System.out.printf("%1$.2f", d);
where d is your variable or number
or
DecimalFormat f = new DecimalFormat("##.00"); // this will helps you to always keeps in two decimal places
System.out.println(f.format(d));
You need to use something like NumberFormat with appropriate locale to format
NumberFormat.getCurrencyInstance().format(bigDecimal);
BigDecimal.setScale would work.
The below code may help.
protected String getLocalizedBigDecimalValue(BigDecimal input, Locale locale) {
final NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);
numberFormat.setGroupingUsed(true);
numberFormat.setMaximumFractionDigits(2);
numberFormat.setMinimumFractionDigits(2);
return numberFormat.format(input);
}
You can use a custom annotation in this manner:
The custom annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface FormatBigDecimal {
String format() default "0.00";
}
And then you can apply the annotation on a field and in the constructor call the implementation method as shown below:
public class TestClass {
#FormatBigDecimal
private BigDecimal myDecimal;
public TestClass(BigDecimal myDecimal) throws ParseException, IllegalAccessException {
this.myDecimal = myDecimal;
formatBigDecimalFields();
}
public void setMyDecimal(BigDecimal myDecimal) {
this.myDecimal = myDecimal;
}
public BigDecimal getMyDecimal() {
return myDecimal;
}
/**
In the above method, we are using reflection to get all the fields declared in the class. Then, we are iterating over all the fields and checking whether the #FormatBigDecimal annotation is present on the field. If it is present, we are making the field accessible and getting its value.
We are also getting the format string from the #FormatBigDecimal annotation and using it to create a DecimalFormat object with the desired format. Then, we are formatting the value of the field using the DecimalFormat object and storing the formatted value in a string.
Finally, we are parsing the formatted value back into a BigDecimal object and setting it as the value of the field.
**/
public void formatBigDecimalFields() throws IllegalAccessException, ParseException {
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(FormatBigDecimal.class)) {
field.setAccessible(true);
BigDecimal value = (BigDecimal) field.get(this);
FormatBigDecimal formatAnnotation = field.getAnnotation(FormatBigDecimal.class);
String formatString = formatAnnotation.format();
NumberFormat format = NumberFormat.getNumberInstance(Locale.US);
DecimalFormat decimalFormat = (DecimalFormat) format;
decimalFormat.applyPattern(formatString);
String formattedValue = decimalFormat.format(value);
BigDecimal formattedDecimal = new BigDecimal(formattedValue);
field.set(this, formattedDecimal);
}
}
}
}
To Use it:
TestClass testClass = new TestClass(new BigDecimal("10000.899"));
System.out.println(testClass.getMyDecimal());
will give: 10000.90
I want my data structures to be custom formatted.
e.g. I have a DS
Address {
string house_number,
string street,
string city,
long pin_code,
}
Now, I want to associate certain conversion specifiers with each of these fields.
e.g. house_number -> H
street -> S,
city -> C,
pin_code -> P
...
So that something like
myPrintWriter.printf("Mr A lives in %C", address_instance)
yields "Mr A lives in boston" (if address_instance.city = boston) etc..
It seems there is no easy way to do this. java.util.Formatter seems to be final. The only customization it provides is via the interface Formattable, but that helps in customizing the 's' conversion specifier only.
Is there a way to add our custom conversion specifiers? Any help will be much appreciated.
Thanks,
It seems there is no easy way to do this. java.util.Formatter seems to be final.
That's true, but you can still use composition. I would do something like the following:
class ExtendedFormatter {
private Formatter defaultFormatter;
// provide the same methods like the normal Formatter and pipe them through
// ...
// then provide your custom method, or hijack one of the existing ones
// to extend it with the functionality you want
// ...
public Formatter format(String format, Object... args) {
// extract format specifiers from string
// loop through and get value from param array
ExtendedFormattable eft = (ExtendedFormattable)args1;
String specifierResult = eft.toFormat(formatSpecifier); // %C would return city
// use specifierResult for the just queried formatSpecifier in your result string
}
}
The hard part is to know how to attach the different format specifiers to the fields you want to output. The first way I can think of, is to provide your own ExtendedFormattable interface that each class that should be used with the ExtendedFormatter can implement, and return the according values for your custom format specifiers. That could be:
class Address implements ExtendedFormattable {
public String toFormat(String formatSpecifier) { // just an very simple signature example
// your custom return values here ...
}
}
There's also annotations, but I think that's not a very viable way.
A sample call would look like:
ExtendedFormatter ef = new ExtendedFormatter();
ef.format("Mr A lives in %C", address_instance);
I believe you will need to write your own formatter which works the way you want.
E.g. eng, spa, ita, ger
I could iterate all locales and compare the codes, but I wonder whether there is a more elegant & performant way to achieve this....
Thanks a lot for any hints :)
I don't know if there's an easy way to convert the 3-letter to the 2-letter versions, but in a worse case scenario, you could create a Map of them, like so:
String[] languages = Locale.getISOLanguages();
Map<String, Locale> localeMap = new HashMap<String, Locale>(languages.length);
for (String language : languages) {
Locale locale = new Locale(language);
localeMap.put(locale.getISO3Language(), locale);
}
Now you can look up locales using things like localeMap.get("eng");
Edit: Modified the way the map is created. Now there should be one object per language.
Edit 2: It's been a while, but changed the code to use the actual length of the languages array when initializing the Map.
You can use constructor Locale(String language), where language is the 2 letter ISO-639-1 code. I think the easiest way to convert ISO-639-2 to ISO-639-1 would be to create HashMap<String,String> constant.
Some modified code from my project, which has a similar requirement. We have our own historical timezone format so we can't use standard libraries.
public class MyProjectTimeZoneFactory {
private static Map timeZoneDb;
/**
* Set up our timezone id mappings; call this from any constructor
* or static method that needs it.
*/
private static void init() {
if(null == TimeZoneDb) {
timeZoneDb = new HashMap(); // Maybe a TreeMap would be more appropriate
timeZoneDb.put(" ","GMT+00");
timeZoneDb.put("EAD ","GMT+10");
timeZoneDb.put("JST ","GMT+9");
// etc.
}
}
public static TimeZone getTimeZone(String id)
throws CommandFormatException {
init();
TimeZone tz;
if(timeZoneDb.containsKey(id)) {
tz = TimeZone.getTimeZone((String)timeZoneDb.get(id));
} else {
throw new CommandFormatException("Invalid Timezone value");
}
return tz;
}
}
You could argue that it would be better to have the map in configuration rather than code - perhaps in a properties file. That may be true - but do remember the Pragmatic Programmers' rule 'Your not going to need it'.