Java GSON Escaping backslashes in a custom serializer - java

I need to serialize a Java LocalDate (e.g. 2020-02-04) into the following format:
"myProperty":"\/Date(-2209165200000+0100)\/", as the interface we work with requests that format.
We are currently using the newest version of GSON (2.8.6)
Our custom LocalDate Serializer looks like this at the moment:
public class LocalDateSerializer implements JsonSerializer<LocalDate> {
#Override
public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) {
Instant instant = src.atStartOfDay(ZoneId.systemDefault()).toInstant();
long timeInMillis = instant.toEpochMilli();
ZoneOffset offset = OffsetDateTime.now().getOffset();
return context.serialize("\\/Date(" + timeInMillis + offset + ")\\/");
}
}
The issue now is that, as far as I know, that the context.serialize function escapes the double backslashes, so the final result is "myProperty":"\\/Date(-2209165200000+0100)\\/" instead of "myProperty":"\/Date(-2209165200000+0100)\/"
And in Java 1.8 you are not allowed to simply have a String like "\/", as this will result in a compiler error.
Is there any simple way of getting our result with single backslashes?
Thanks and kind regards :)
Marco

As specified in RFC 7159, \/ represents an escaped forward slash and decodes to \.
Consequently, you want to return context.serialize("/Date(" + timeInMillis + offset + ")/");.

We fixed our problem by returning a new JsonPrimitive instead of using context
So we are using return new JsonPrimitive("/Date(" + timeInMillis + offset + ")/"); now and everything works fine now. Still thanks for the input though, much appreciated!

Related

Is this additional check for parsing a string to LocalDate object necessary?

I was writing some tests for some legacy code that validates a user's date of birth. I encounter the following method in the class. My doubt is that whether the if statement in the try block is necessary. From my understanding, if the parse function returns a LocalDate object successfully, then date.toString() should always equal to the input dobstr, and there's no need to do an additional check. Am I missing anything? I could not think of any case that we need this extra check. Please help. Thanks!
private LocalDate format(String dobStr) throws Exception {
LocalDate date = null;
try {
date = LocalDate.parse(dobStr, DateTimeFormatter.ISO_DATE);
if (!dobStr.equals(date.toString())) {
throw new DateTimeParseException("some message");
}
}
catch (DateTimeParseException ex) {
throw ex;
}
return date;
}
this is what I found in the source code for DateTimeFormatter.ISO_DATE
public static final DateTimeFormatter ISO_DATE;
static {
ISO_DATE = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(ISO_LOCAL_DATE)
.optionalStart()
.appendOffsetId()
.toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
}
The only reason that I could see for doing a toString() check would be to avoid lenient issue: the parser may be lenient and try to interpret wrong values (for example: 2020-12-32 could be interpreted as 2021-01-01).
DateFormat allows the parser to be lenient
The same behaviour is offered by DateTimeFormatter and the default value is ResolverStyle.SMART.
If you want to remove it, you should check if DateTimeFormatter.ISO_DATE is ResolverStyle.STRICT by default or not. Assuming it is not STRICT by default, your code could be:
private LocalDate format(String dobStr) throws Exception {
return LocalDate.parse(dobStr, DateTimeFormatter.ISO_DATE.withResolverStyle(ResolverStyle.STRICT));
}
TL;DR: The check makes a difference
If the string contains an unwanted offset ID, you will still be able to parse it using DateTimeFormatter.ISO_DATE. But since a LocalDate cannot have an offset (this is what local in the name means), the result of toString() will never have that offset ID, so the strings will not be equal.
DateTimeFormatter.ISO_DATE accepts an optional offset ID after the date. So if you are parsing 2020-08-12z or 2020-08-12+01:02:03, the custom exception would be thrown. Except for a detail: DateTimeParseException hasn’t got a constructor that matches a single string argument, so the code doesn’t compile. I reckon that this comes from sloppy copy-paste from the original code.
To demonstrate:
String dobStr = "2020-08-12+01:02:03";
LocalDate date = LocalDate.parse(dobStr, DateTimeFormatter.ISO_DATE);
String asStringAgain = date.toString();
System.out.format("Original string: %s; result of toString(): %s; equal? %s%n",
dobStr, asStringAgain, dobStr.equals(asStringAgain));
Output is:
Original string: 2020-08-12+01:02:03; result of toString():
2020-08-12; equal? false
How to obviate the check
Unless you require a custom exception in the case of an unwanted offset, the method may be written much more simply:
private LocalDate format(String dobStr) throws Exception {
return LocalDate.parse(dobStr, DateTimeFormatter.ISO_LOCAL_DATE);
}
DateTimeFormatter.ISO_LOCAL_DATE does not accept any offset in the string. And it is strict just like DateTimeFormatter.ISO_DATE, so we know that toString() would create the same string again.
Furthermore you may declare the method static, and you may leave out throws Exception since DateTimeParseException is an unchecked exception.
Link
Documentation of DateTimeFormatter.ISO_DATE

Format a float as fixed point with Jackson

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.

Convert String date to Object yields Invalid time zone indicator '0'

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

Converting java.lang.double to org.joda.time.Instant

I have looked around a lot. I am very new to Java, and I am trying to cast a Double into an Instant. The use case is for the Google Dataflow Java SDK. The Double is a UNIX timestamp from a file I read using TextIO. When I System.out.println(row.get("timestamp")) I indeed get UNIX timestamps. When I do System.out.println(row.get("timestamp").getClass().getName()), then I get java.lang.double. what I have is as follows:
static class ExtractTimestamp extends DoFn<TableRow, TableRow> {
#Override
public void processElement(ProcessContext c) {
TableRow row = c.element();
Instant timestamp = (Instant) row.get("timestamp");
c.outputWithTimestamp(row, timestamp);
}
}
The error I am getting is:
java.lang.Double cannot be cast to org.joda.time.Instant
The problem is that I want to cast the UNIX timestamps that are in double to Instants so I can pass it to outputWithTimestamp. I believe this should be a trivial problem, but I wasn't able to find the solution yet. Thank you.
You can't "cast" a Double to an Instant. You need to pass your timestamp as a constructor argument:
Instant timestamp = new Instant(((Number)row.getTimestamp("timestamp")).longValue());
This assumes the "timestamp" value is in milliseconds. If it's seconds, just multiply by 1000.

How to get Locale from its String representation in Java?

Is there a neat way of getting a Locale instance from its "programmatic name" as returned by Locale's toString() method? An obvious and ugly solution would be parsing the String and then constructing a new Locale instance according to that, but maybe there's a better way / ready solution for that?
The need is that I want to store some locale specific settings in a SQL database, including Locales themselves, but it would be ugly to put serialized Locale objects there. I would rather store their String representations, which seem to be quite adequate in detail.
Method that returns locale from string exists in commons-lang library:
LocaleUtils.toLocale(localeAsString)
Since Java 7 there is factory method Locale.forLanguageTag and instance method Locale.toLanguageTag using IETF language tags.
Java provides lot of things with proper implementation lot of complexity can be avoided. This returns ms_MY.
String key = "ms-MY";
Locale locale = new Locale.Builder().setLanguageTag(key).build();
Apache Commons has LocaleUtils to help parse a string representation. This will return en_US
String str = "en-US";
Locale locale = LocaleUtils.toLocale(str);
System.out.println(locale.toString());
You can also use locale constructors.
// Construct a locale from a language code.(eg: en)
new Locale(String language)
// Construct a locale from language and country.(eg: en and US)
new Locale(String language, String country)
// Construct a locale from language, country and variant.
new Locale(String language, String country, String variant)
Please check this LocaleUtils and this Locale to explore more methods.
See the Locale.getLanguage(), Locale.getCountry()... Store this combination in the database instead of the "programatic name"...
When you want to build the Locale back, use public Locale(String language, String country)
Here is a sample code :)
// May contain simple syntax error, I don't have java right now to test..
// but this is a bigger picture for your algo...
public String localeToString(Locale l) {
return l.getLanguage() + "," + l.getCountry();
}
public Locale stringToLocale(String s) {
StringTokenizer tempStringTokenizer = new StringTokenizer(s,",");
if(tempStringTokenizer.hasMoreTokens())
String l = tempStringTokenizer.nextElement();
if(tempStringTokenizer.hasMoreTokens())
String c = tempStringTokenizer.nextElement();
return new Locale(l,c);
}
Option 1 :
org.apache.commons.lang3.LocaleUtils.toLocale("en_US")
Option 2 :
Locale.forLanguageTag("en-US")
Please note Option 1 is "underscore" between language and country , and Option 2 is "dash".
If you are using Spring framework in your project you can also use:
org.springframework.util.StringUtils.parseLocaleString("en_US");
Documentation:
Parse the given String representation into a Locale
This answer may be a little late, but it turns out that parsing out the string is not as ugly as the OP assumed. I found it quite simple and concise:
public static Locale fromString(String locale) {
String parts[] = locale.split("_", -1);
if (parts.length == 1) return new Locale(parts[0]);
else if (parts.length == 2
|| (parts.length == 3 && parts[2].startsWith("#")))
return new Locale(parts[0], parts[1]);
else return new Locale(parts[0], parts[1], parts[2]);
}
I tested this (on Java 7) with all the examples given in the Locale.toString() documentation: "en", "de_DE", "_GB", "en_US_WIN", "de__POSIX", "zh_CN_#Hans", "zh_TW_#Hant-x-java", and "th_TH_TH_#u-nu-thai".
IMPORTANT UPDATE: This is not recommended for use in Java 7+ according to the documentation:
In particular, clients who parse the output of toString into language, country, and variant fields can continue to do so (although this is strongly discouraged), although the variant field will have additional information in it if script or extensions are present.
Use Locale.forLanguageTag and Locale.toLanguageTag instead, or if you must, Locale.Builder.
Old question with plenty of answers, but here's more solutions:
Get Locale From String
Converts a String to a Locale
There doesn't seem to be a static valueOf method for this, which is a bit surprising.
One rather ugly, but simple, way, would be to iterate over Locale.getAvailableLocales(), comparing their toString values with your value.
Not very nice, but no string parsing required. You could pre-populate a Map of Strings to Locales, and look up your database string in that Map.
Might be late but if someone looking for simple solution:
Instead of toString() set Locale string by using: String langTag = localeObj.toLanguageTag();
Store langTag in DB or wherever you want
At the consumer side get String lanTag = fromDB(); // or wherever
Get the Locale by: Locale locale = Locale.forLanguageTag(langTag);
No additional dependency needed!
You can use this on Android. Works fine for me.
private static final Pattern localeMatcher = Pattern.compile
("^([^_]*)(_([^_]*)(_#(.*))?)?$");
public static Locale parseLocale(String value) {
Matcher matcher = localeMatcher.matcher(value.replace('-', '_'));
return matcher.find()
? TextUtils.isEmpty(matcher.group(5))
? TextUtils.isEmpty(matcher.group(3))
? TextUtils.isEmpty(matcher.group(1))
? null
: new Locale(matcher.group(1))
: new Locale(matcher.group(1), matcher.group(3))
: new Locale(matcher.group(1), matcher.group(3),
matcher.group(5))
: null;
}
Well, I would store instead a string concatenation of Locale.getISO3Language(), getISO3Country() and getVariant() as key, which would allow me to latter call Locale(String language, String country, String variant) constructor.
indeed, relying of displayLanguage implies using the langage of locale to display it, which make it locale dependant, contrary to iso language code.
As an example, en locale key would be storable as
en_EN
en_US
and so on ...
Because I have just implemented it:
In Groovy/Grails it would be:
def locale = Locale.getAvailableLocales().find { availableLocale ->
return availableLocale.toString().equals(searchedLocale)
}

Categories

Resources