Jackson serialize Joda DateTime from simple String - java

I am trying to extract a value of predefined type (Boolean, Integer, joda.DateTime) from an arbitrary json that is sent as a String.
Eg: {"node1":{"node2":"2019-01-01T05:00:00.000Z"}}} and say I know that the value in this Json is a DateTime and I can extract the value 2019-01-01T05:00:00.000Z from this Json and disabled SerializationFeature.WRITE_DATES_AS_TIMESTAMPS.
When I try to serialize a simple String representation "1972-12-28T12:00:01.000Z" of org.joda.time.DateTime, it fails with JsonParseException: Unexpected character. However serialization will succeed for Booleans or DateTime string inside a TextNode.
I have have registered com.fasterxml.jackson.datatype.joda.JodaModule with my object mapper.
I have tried a few things, see the Junit test below
public class Tester {
public static class Bean {
public void Bean(){}
public DateTime start;
}
#Test
public void testJodaJsonSerialization() throws Exception{
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JodaModule());
final String INPUT_JSON = "{\"start\" : \"1972-12-28T12:00:01.000Z\"}";
Bean bean = objectMapper.readValue(INPUT_JSON, Bean.class);
assertNotNull(bean.start);
System.out.println(objectMapper.writeValueAsString(bean)); //serializing as part of an object works
String boolAsString = "true";
Boolean aBoolean = objectMapper.readValue(boolAsString, Boolean.class); //works for bool (simple type)
System.out.println(aBoolean);
String dateAsTextNode = objectMapper.writeValueAsString(new TextNode("1972-12-28T12:00:01.000Z")); //works for TextNode
System.out.println("dateAsTextNode: " + dateAsTextNode);
DateTime dateTime = objectMapper.readValue(dateAsTextNode, DateTime.class);
System.out.println(dateTime);
JsonNode jsonRoot = objectMapper.readTree(INPUT_JSON);
String datetimeAsString = jsonRoot.get("start").asText();
objectMapper.readValue(objectMapper.writeValueAsString(new TextNode(datetimeAsString)), DateTime.class); //this workaround will work
objectMapper.readValue(objectMapper.writeValueAsString(new TextNode(boolAsString)), Boolean.class);
String dateAsString = "1972-12-28T12:00:01.000Z";
objectMapper.readValue(dateAsString, DateTime.class); //but this fails
}
}
I expect String serialization to work just like it does on the TextNode

Your String
String dateAsString = "1972-12-28T12:00:01.000Z";
contains the content
1972-12-28T12:00:01.000Z
which is not valid JSON, so Jackson cannot parse it.
It would be valid JSON if it contained leading quotes, so
String dateAsString = "\"1972-12-28T12:00:01.000Z\"";
and then parsing would succeed.

You can configure the pattern of the date format on the ObjectMapper level:
Value dateFormat = Value.forShape(Shape.STRING)
.withPattern("MM/dd/yyyy HH:mm:ss")
.withTimeZone(TimeZone.getTimeZone("UTC"));
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule())
.configOverride(DateTime.class).setFormat(dateFormat);

Related

Error thrown when deserialising 2D Json Array using Jackson

I am trying to deserialize a two things from JSON. The format of the first one is as follows:
String json = "[{\"name\":\"Random\"," +
"\"coordinates\":[[-3.1,55.4],[-3.1,55.9],[-3.7,55.3],[-3.8,55.7],[-3.0,55.8]]}]";
This is the second:
String json2 = "[{\"name\":\"Random\"," + "\"longitude\":-3.1, \"latitude\":55}]
My code is simple, and is as follows:
ObjectMapper mapper = new ObjectMapper();
var asArray = mapper.readValue(json, NoFlyZone[].class);
var asArray2 = mapper.readValue(json, LngLat.class);
The NoFlyZone class:
record NoFlyZone(LngLat[] coordinates) {
#JsonIgnoreProperties("name")
NoFlyZone (#JsonProperty("coordinates") double[][] coordinates) {
this(doubleArrayToLngLatArray(coordinates));
}
private static LngLat[] doubleArrayToLngLatArray(double[][] coordinates) {
var coordinateArray = new LngLat[coordinates.length];
for (int i = 0; i < coordinates.length; i++) {
coordinateArray[i] = new LngLat(coordinates[i][0], coordinates[i][1]);
}
System.out.println(coordinateArray);
return coordinateArray;
}
}
And finally, the LngLat class:
record LngLat(double lng, double lat) {
LngLat (#JsonProperty("longitude") double lng,
#JsonProperty("latitude") double lat) {
this.lng = lng;
this.lat = lat;
}
}
I have tried deserialising them in the way shown above, but a MismatchedInputException is thrown when trying to deserialize the first string, with the error message "Cannot deserialize value of type uk.ac.ed.inf.LngLat from Array value (token JsonToken.START_ARRAY)...". I'm not sure why this is happening, so any help would be appreciated.
I have also tried adding the annotation
#JsonFormat(shape = JsonFormat.Shape.ARRAY)
and fixing it as detailed in Alexander's answer, but then the second string throws an error when attempting to be deserialised.
Since your record LngLat is represented as JSON-array (like "[-3.1, 55.4]") you need to customize its deserialization.
And for that you can use #JsonFormat annotation providing the attribute shape with the value of JsonFormat.Shape.ARRAY. That would instruct Jackson to populate the record properties from the array in order of their declaration.
#JsonFormat(shape = JsonFormat.Shape.ARRAY)
record LngLat(double lng, double lat) {}
And enclosing record NoFlyZone would be simplified to (special method for parsing array of LngLat is redundant):
#JsonIgnoreProperties("name")
record NoFlyZone(LngLat[] coordinates) {}
Usage example:
String json = "[{\"name\":\"Random\"," +
"\"coordinates\":[[-3.1,55.4],[-3.1,55.9],[-3.7,55.3],[-3.8,55.7],[-3.0,55.8]]}]";
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json, new TypeReference<List<NoFlyZone>>() {}));
Output:
note: toString() method of the NoFlyZone has been overridden to display the array correctly
[NoFlyZone{coordinates=[LngLat[lng=-3.1, lat=55.4], LngLat[lng=-3.1, lat=55.9], LngLat[lng=-3.7, lat=55.3], LngLat[lng=-3.8, lat=55.7], LngLat[lng=-3.0, lat=55.8]]}]
Update
If you need to support the two entirely different structures of JSON, then you also need to customize deserialization on the NoFlyZone level (because its JSON-shape differs).
One of the ways to do that is to introduce the factory method annotated with #JsonCreator. It would expect a single argument of type Map<String, JsonNode> in order to be able to funnel all the properties thought it.
We also need to set the attribute of ignoreUnknown of #JsonIgnoreProperties to true.
Note: the definition of LngLat remains the same (as shown above annotated with #JsonFormat).
#JsonIgnoreProperties(ignoreUnknown = true)
public record NoFlyZone(LngLat[] coordinates) {
#JsonCreator
public static NoFlyZone getInstance(Map<String, JsonNode> fields) throws IOException {
boolean isArray = fields.containsKey("coordinates");
LngLat[] longLat;
if (isArray) {
ObjectReader reader = new ObjectMapper().readerFor(LngLat[].class);
longLat = reader.readValue(fields.get("coordinates")); // parsing JsonNode which corresponds to "coordinates" property
} else {
longLat = new LngLat[] { // creating a single-element array
new LngLat(
Double.parseDouble(fields.get("longitude").toString()),
Double.parseDouble(fields.get("latitude").toString())
)
};
}
return new NoFlyZone(longLat);
}
// toString()
}
Usage example:
String json1 = "[{\"name\":\"Random\"," +
"\"coordinates\":[[-3.1,55.4],[-3.1,55.9],[-3.7,55.3],[-3.8,55.7],[-3.0,55.8]]}]";
String json2 = "[{\"name\":\"Random\"," + "\"longitude\":-3.1, \"latitude\":55}]";
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json1, new TypeReference<List<NoFlyZone>>() {}));
System.out.println(mapper.readValue(json2, new TypeReference<List<NoFlyZone>>() {}));
Output:
[NoFlyZone{coordinates=[LngLat[lng=-3.1, lat=55.4], LngLat[lng=-3.1, lat=55.9], LngLat[lng=-3.7, lat=55.3], LngLat[lng=-3.8, lat=55.7], LngLat[lng=-3.0, lat=55.8]]}]
[NoFlyZone{coordinates=[LngLat[lng=-3.1, lat=55.0]]}]

Issue with Map<Range,List> de/serialization with Object Mapper

In the following method when trying to use deserialized Map created from object mapper, upon reading the key value JVM is throwing an error.
public void someMethod() {
List<String> list = Arrays.asList("1234", "12314");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date1 = dateFormat.parse("2021-11-25 00:00:00");
Date date2 = dateFormat.parse("2021-11-26 00:00:00");
Range<Instant> range = Range.closed(date1.toInstant(), date2.toInstant());
// Google range
Map<Range<Instant>, List<String>> map = ImmutableMap.of(
rangeA, listA,
);
// Jackson object mapper
ObjectMapper mapper = new ObjectMapper();
final String serializedMap = mapper.writeValueAsString(map);
// both de/serialized object are created as expected.
Map<Range<Instant>, List<String>> deserializedMap = mapper.readValue(serializedMap, Map.class);
//
System.out.println(serializedMap);
// {"[2021-11-25T08:00:00Z..2021-11-26T08:00:00Z]":["1234","12314"],"[2021-11-25T20:00:00Z..2021-11-26T20:00:00Z]":["9999","1010","1234"]}
System.out.println(deserializedMap);
// {[2021-11-25T08:00:00Z..2021-11-26T08:00:00Z]=[1234, 12314], [2021-11-25T20:00:00Z..2021-11-26T20:00:00Z]=[9999, 1010, 1234]}
// This line is throwing error
deserializedMap.entrySet().stream().map(rangeListEntry -> rangeListEntry.getKey()).forEach(System.out::println);
}
Error: java.lang.ClassCastException: java.lang.String cannot be cast to com.google.common.collect.Range
Couple things I have tried but didn't work:
// for Java 8 related feature
mapper.registerModule(new Jdk8Module());
// https://github.com/FasterXML/jackson-modules-java8
mapper.registerModule(new JavaTimeModule());
// mapper.registerModule(new JodaModule());
// for collections.
mapper.registerModule(new GuavaModule());
Am I doing anything wrong here or is this a bug ?
Try registering GuavaModule, found in the jackson-datatype-guava artifact.
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new GuavaModule());

How to convert any String into valid JSON format in JAVA

I am getting the String in following format which is causing problem while parsing using Jackson ObjectMapper readTree api. Code used to parse the given String is
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
objectMapper.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
JsonNode rootNode = objectMapper.readTree(inputString);
It is throwing exception when it encounters "7" in the below String
{
OBJECT_CONVERSION_ERROR:"Failed..."
Portal:{
7061:"User is....."}
}
How to convert such a String in Valid JSON format using JAVA ?
I am using jackson-all-1.9.11.jar
Below is my exception message
org.codehaus.jackson.JsonParseException: Unexpected character ('7' (code 55)): was expecting either valid name character (for unquoted name) or double-quote (for quoted) to start field name
at [Source: java.io.StringReader#3fb1549b; line: 1, column: 1433]
Is there any way to convert the input String in valid json format before passing it to Object Mapper for parsing it ?
The Jackson ObjectMapper expects double-quoted field names in its default configuration.
To change this behavior you could do the following:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
JSONParser parser = new JSONParser(); JSONObject json = (JSONObject)
parser.parse(stringToParse);
Read more: http://www.java67.com/2016/10/3-ways-to-convert-string-to-json-object-in-java.html#ixzz4y2fACQlg

Escaping nested double quotes during objectMapper.readValue()

I am trying to read an object into my model using objectMapper
ObjectMapper objectMapper = new ObjectMapper();
result = objectMapper.readValue(process.getData(), params.class);
under one of the keys of params there is a string
href=\"${organizationParams.get(\"info.facebook\")}\">
so after readValue happens the string looks like
href="${organizationParams.get("info.facebook")}">
and then later on I have call to Jsoup.clean() which truncates the string to
href="${organizationParams.get("
This is not desirable. Any ideas on how to retain the string after Jsoup.clean?

Create Json String

I use Jackson library to generate json string like this:
ObjectMapper objectMapper = new ObjectMapper();
String str = objectMapper.writeValueAsString(model);
and this snipped code for instance generate somtething like this:
{"x" : "This is x", "y" : "This is y"}
but I want to generate something like this:
{'x' : 'This is x', 'y' : 'This is y'}
I mean how can I change the double quote string with single quote string.I try to change code like this:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
String str = objectMapper.writeValueAsString(model);
but this snipped code generate the first one.
and of course I can handle this problem with replace method but I want Jackson library do this for me.
How can I handle this problem?
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); is about allowing single quotes in deserializing (JSON to Objects), not serializing (Objects to JSON) as you want.
In serializing, the issue seems to be with Jackson 1.X's default serializer. Below is the code that Jackson uses to write the String values. As you can see, the double quotes are hard coded and thus unchangeable through configuration:
#Override
public void writeString(String text)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write text value");
if (text == null) {
_writeNull();
return;
}
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = '"'; // <----------------- opening quote
_writeString(text); // <----------------- string actual value
// And finally, closing quotes
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = '"'; // <----------------- closing quote
}
To achieve what you want, there are at least two options:
1: Replace the quotes using Regex:
This is a safe approach because Jackson gives the double quotes (") already escaped (\"). All you have to do is escape the single quotes and switch the " around properties names and values:
ObjectMapper objectMapper = new ObjectMapper();
String str = objectMapper.writeValueAsString(model);
System.out.println("Received.: "+str);
str = str.replaceAll("'", "\\\\'"); // escapes all ' (turns all ' into \')
str = str.replaceAll("(?<!\\\\)\"", "'"); // turns all "bla" into 'bla'
System.out.println("Converted: "+str);
Output:
Received.: {"x":"ab\"c","y":"x\"y'z","z":15,"b":true}
Converted: {'x':'ab\"c','y':'x\"y\'z','z':15,'b':true}
Or 2: User a custom JsonSerializer on every String field
Declare the custom serializer:
public class SingleQuoteStringSerializer extends JsonSerializer<String> {
#Override
public void serialize(String str, JsonGenerator jGen, SerializerProvider sP)
throws IOException, JsonProcessingException {
str = str.replace("'", "\\'"); // turns all ' into \'
jGen.writeRawValue("'" + str + "'"); // write surrounded by single quote
}
}
Use it in the fields you want to single quote:
public class MyModel {
#JsonSerialize(using = SingleQuoteStringSerializer.class)
private String x;
...
And proceed as usual (QUOTE_FIELD_NAMES == false is used to unquote the field names):
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
String str = objectMapper.writeValueAsString(model);
System.out.println("Received.: "+str);
Output:
Received.: {x:'ab"c',y:'x"y\'z',z:15,b:true}
Note: Since you seem to be wanting to embed the JSON into another, this last approach may also require escaping the "s (see x:'ab"c').
Configure ObjectMapper in the following way:
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
//this may be what you need
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
Try looking into gson. It would look like this in gson.
House myHouse = new House();
Gson gson = new Gson();
String json = gson.toJson(myHouse);
Done...
http://code.google.com/p/google-gson/
As I said in the comment that's not valid JSON and it doesn't make any sense to escape it. You should handle it in a different way.
You should put that object inside a property.
I think you want to have something like
{"myString":"{\"fake json\":\"foo\"}"}
instead you should have:
{"myString":{"fake json":"foo"}}
That should be the proper way to handle this.

Categories

Resources