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());
Related
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]]}]
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);
I’m currently writing Java client code that gets a JSON response from a rest service. For my JSON response, I need to deserialize it to a pojo. If the JSON’s outer most wrappers are square brackets that enclose a list of objects, what Jackson annotation can I use to load it to an array or ArrayList?
The JSON looks like this:
[{"key1": "val1"}, {"key2": "val2"}, {"key3": "val3"}]
Jackson can unmarshall json directly to your object, vise-versa.
public void givenJsonArray_whenDeserializingAsArray_thenCorrect()
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
List<MyDto> listOfDtos = Lists.newArrayList(
new MyDto("a", 1, true), new MyDto("bc", 3, false));
String jsonArray = mapper.writeValueAsString(listOfDtos);
// [{"stringValue":"a","intValue":1,"booleanValue":true},
// {"stringValue":"bc","intValue":3,"booleanValue":false}]
MyDto[] asArray = mapper.readValue(jsonArray, MyDto[].class);
assertThat(asArray[0], instanceOf(MyDto.class));
}
source:https://www.baeldung.com/jackson-collection-array
you can try this
String json = "[{\"key1\": \"val1\"}, {\"key2\": \"val2\"}, {\"key3\": \"val3\"}]";
ObjectMapper mapper = new ObjectMapper();
ArrayList<Map<String,String>> list = mapper.readValue(json, Object.class);
for (Map map : list)
for (Object key :map.keySet())
System.out.println("key: "+key.toString()+" value:"+map.get(key));
//result
//key: key1 value:val1
//key: key2 value:val2
//key: key3 value:val3
I have an service class as below:
public class RulesService {
#PersistenceContext
private EntityManager em;
public JSONArray getReportingTableData(String Query) {
List<Object> list = em.createNativeQuery(Query).getResultList();
return /*convert the above list as json array**/;
}
}
So, if the query is "select col1 as name,col2 as agefrom table1" then my jsonArray should be
[{"name":"test","age":"24"},{"name":"test1","age":"26"}]
I don't want to use pojo or entity class here, because the query will get change dynamically and there are many number of tables here, so I don't want to create seperate java class for each table.That is the reason am trying to make it as a JSONArray.
Can anyone please give me the right solution do it.
You could do that with Jackson's ObjectMapper. This tutorial is very interesting.
List<Object> list = em.createNativeQuery(Query).getResultList();
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(out, list);
final byte[] data = out.toByteArray();
System.out.println(new String(data));
you can use ObjectMapper to do dynamically.
public JSONArray getReportingTableData(String Query) {
List<Object> list = em.createNativeQuery(Query).getResultList();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
String arrayToJson = objectMapper.writeValueAsString(list);
JSONArray array = new JSONArray(arrayToJson);
return /*convert the above list as json array**/;
}
I guess you want to do like bellow. your query result is list of array. [["test","24"],["test2","26"]] and you want to convert it to key-value
List<Map<String,String>> result = listOfarray.stream()
.map(arr->{
Map<String,String> map = new HashMap<>();
map.put("name",arr[0]);
map.put("age",arr[1]);
return map;
})
.collect(Collectors.toList());
Sorry guys, I might be late but someone might find this interesting and save his/her day.
Reference
[https://gist.github.com/baxtheman/44fd1601380d415eeec53d9e6d5587dc][1]
public List<ObjectNode> getQuery(
Integer anno,
Integer settimana) {
Query q = em.createNativeQuery(
"NATIVE SQL....",Tuple.class);
List<Tuple> results = q.getResultList();
List<ObjectNode> json = _toJson(results);
return json;
}
private List<ObjectNode> _toJson(List<Tuple> results) {
List<ObjectNode> json = new ArrayList<ObjectNode>();
ObjectMapper mapper = new ObjectMapper();
for (Tuple t : results)
{
List<TupleElement<?>> cols = t.getElements();
ObjectNode one = mapper.createObjectNode();
for (TupleElement col : cols)
{
one.put(col.getAlias(), t.get(col.getAlias()).toString());
}
json.add(one);
}
return json;
}
Its a late answer but got it when i needed it.
The pretty easy and simple thing worked for me is
String[] columns = {"id","name","salary","phone","address", "dob"};
String query = "SELECT id,name,salary,phone,address,dob from users ";
List<Object[]> queryResp = em.createNativeQuery(query).getResultList();
List<Map<String,String>> dataList = new ArrayList<>();
for(Object[] obj : queryResp) {
Map<String,String> row = new HashMap<>(columns.length);
for(int i=0; i<columns.length; i++) {
if(obj[i]!=null)
row.put(columns[i], obj[i].toString());
else
row.put(columns[i], "");
}
dataList.add(row);
}
//Creating the ObjectMapper object
ObjectMapper mapper = new ObjectMapper();
//Converting the Object to JSONString
String jsonString = mapper.writeValueAsString(dataList);
I am mapping Object (i don't have control over) to jsonString, after mapping I get duplicate key-value pairs in the JSON,
example
{
"id":"123",
"email":"someEmail#gmail.com",
"UserName":"someOne",
"EMAIL":"someEmail#gmail.com"
}
the duplicate is exactly the same except that it is in uppercase letters.
I am trying to get a jsonInString format without the duplication. Something like this:
{
"id":"123",
"email":"someEmail#gmail.com",
"UserName":"someOne"
}
I have tried
String jsonInStringWithOutDuplication=mapper.enable(
JsonParser.Feature.STRICT_DUPLICATE_DETECTION).writeValueAsString(users);
with no luck, any suggestions?
If you don't find a way to configure the ObjectMapper to filter out duplicate attributes, you can serialize the problematic object to JSON, then serialize the JSON to a Map object, merge duplicate attributes and serialize it to JSON again:
Map<String, String> objectWithDuplicates = new HashMap<>();
map.put("name", "MyName");
map.put("email", "em#ail");
map.put("EMAIL", "em#ail");
ObjectMapper mapper = new ObjectMapper();
String jsonWithDuplicates = mapper.writeValueAsString(objectWithDuplicates);
Map<String, Object> attributesWithDuplicates = mapper
.readValue(jsonWithDuplicates, Map.class);
Map<String, Object> withoutDuplicates = new HashMap<>();
attributesWithDuplicates.forEach((key, value) -> {
if (! withoutDuplicates.containsKey(key.toLowerCase())) {
withoutDuplicates.put(key.toLowerCase(), value);
}
});
String json = mapper.writeValueAsString(withoutDuplicates);
Jackson's ObjectMapper has a feature that puts the same keys into an array. Isn't it something that could help you?
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new GuavaModule());
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
Multimap resultAsMultimap = mapper.readValue(json, Multimap.class);
System.out.println(resultAsMultimap);