From the API client, I am sending
{
"scheduledDateTime": "27-12-2020 08:55:46",
"subscriptionClosedBy": "30-12-2020 08:55:46",
"presenter":{"first":"Jone Doe","second":"Senior Tech Lead"}
}
JSON object to my following API endpoint.
public ResponseEntity<ApiResponse> saveWorkshop(#RequestBody Workshop workshop)
Workshop class contains a Pair type attribute as follows.
public class Workshop extends PersistObject {
private LocalDateTime scheduledDateTime;
private LocalDateTime subscriptionClosedBy;
private Pair<String, String> presenter;
}
When the request hits the endpoint exception is thrown as follows.
Caused by:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of org.springframework.data.util.Pair
How can I overcome this issue?
I created the following custom deserializer class
public final class PairDeserializer extends JsonDeserializer<Pair<String, String>> {
static private ObjectMapper objectMapper = null;
#Override
public Pair<String, String> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
if (objectMapper == null) {
objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
JsonNode treeNode = jsonParser.getCodec().readTree(jsonParser);
String first = objectMapper.treeToValue(treeNode.get("first"), String.class);
String second = objectMapper.treeToValue(treeNode.get("second"), String.class);
return Pair.of(first, second);
}
}
and applied it to the Workshop class as follows
#JsonDeserialize(using = PairDeserializer.class)
private Pair<String, String> presenter;
Then the issue was solved.
Related
public class user {
private String planId;
private String eid;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Collection<String> userIds;
}
I have a pojo like above
and the code which is use for creating the json request object is this :
public UserCollection getUserCollection(final user args) {
Map<String, String> headersMap = new HashMap<>();
ObjectMapper jacksonMapper = new ObjectMapper();
jacksonMapper.disable(MapperFeature.USE_ANNOTATIONS); // this line is creating the userIds field to reflect
//By any code we can remove the userId field from the args object
String responseBody = null;
String responseStatus = null;
String jsonRequestBody = jacksonMapper.writeValueAsString(args);
}
I just want to remove userIds from the args by not removing any of the above code.
Thanks in advance.
I don't know how you should solve this without removing the annotation processing code, you should maybe add a custom serializer. You can read further about the topic at here.
public class HelloWorld {
public static void main(String[] args) throws JsonProcessingException {
User myItem = new User("planId", "eId", List.of("1", "2"));
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(User.class, new UserSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(myItem);
}
#Data
#AllArgsConstructor
public static class User {
private String planId;
private String eId;
private Collection<String> userIds;
}
public static class UserSerializer extends StdSerializer<User> {
public UserSerializer() {
this(null);
}
public UserSerializer(Class<User> t) {
super(t);
}
#Override
public void serialize(User value, JsonGenerator gen, SerializerProvider provider) throws IOException{
gen.writeStartObject();
gen.writeStringField("planId", value.planId);
gen.writeStringField("eId", value.eId);
gen.writeEndObject();
}
}
}
If you had to use it without annotations, and cannot add a custom serializer you should map the User class into a more specific class without the field in question and then serialize that one.
I need some help with Jackson library in Java. I have the following class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class MarketHistoryData {
private String countryCode;
#JsonDeserialize(keyUsing = BpTimeDeserializer.class)
private Map<BpTime, Double> hourToExpectedEngagePaidListings;
#JsonDeserialize(keyUsing = BpTimeDeserializer.class)
private Map<BpTime, Double> hourMinuteToExpectedEngagePaidListings;
public MarketHistoryData() {}
...
// getters and setters
}
I understood that Jackson has difficulties with deserializing a map which its keys are an object. Therefore, I added the annotations:
#JsonDeserialize(keyUsing = BpTimeDeserializer.class)
The class BpTimeDeserializer is:
public class BpTimeDeserializer extends KeyDeserializer {
private ObjectMapper mapper = new ObjectMapper();
#Override
public BpTime deserializeKey(String key, DeserializationContext ctxt) throws IOException {
return mapper.readValue(key, BpTime.class);
}
}
Still, I get an error during the deserialization process:
*****.UncheckedExecutionException:
com.fasterxml.jackson.databind.JsonMappingException: Unrecognized
token 'com': was expecting (JSON String, Number, Array, Object or
token 'null', 'true' or 'false') at [Source:
(String)"****BpTime#5922062e[hour=1,minute=0]";
line: 1, column: 1] (through reference chain:
***PacerCycleOutput["campaignIdToFactorComputationData"]->java.util.LinkedHashMap["1011620661"]->****FactorComputationData["selectedMarketHistoryDataForCampaign"]->***MarketHistoryData["hourToExpectedEngagePaidListings"])
Do you know what I can do to overcome this error?
I finally found a way how to resolve this error.
#JsonIgnoreProperties(ignoreUnknown = true)
public class MarketHistoryData {
private String countryCode;
#JsonDeserialize(keyUsing = BpTimeDeserializer.class)
#JsonSerialize(keyUsing = BpTimeSerializer.class)
private Map<BpTime, Double> hourToExpectedEngagePaidListings;
#JsonDeserialize(keyUsing = BpTimeDeserializer.class)
#JsonSerialize(keyUsing = BpTimeSerializer.class)
private Map<BpTime, Double> hourMinuteToExpectedEngagePaidListings;
...
}
public class BpTimeDeserializer extends KeyDeserializer {
private ObjectMapper mapper = new ObjectMapper();
#Override
public BpTime deserializeKey(String key, DeserializationContext ctxt) throws IOException {
String[] keySplit = key.split(" ");
BpTime bpTime = new BpTime();
if (!keySplit[0].equals("-1")) {
bpTime.setHour(Integer.parseInt(keySplit[0]));
}
if (!keySplit[1].equals("-1")) {
bpTime.setMinute(Integer.parseInt(keySplit[1]));
}
return bpTime;
}
}
public class BpTimeSerializer extends StdSerializer<BpTime> {
public BpTimeSerializer() {
this(null);
}
public BpTimeSerializer(Class<BpTime> t) {
super(t);
}
#Override
public void serialize(BpTime value, JsonGenerator generator, SerializerProvider arg2) throws IOException {
generator.writeFieldName(String.format("%s %s", ofNullable(value.getHour()).orElse(-1),
ofNullable(value.getMinute()).orElse(-1)));
}
}
I'm new to Spring-boot and am trying to deserialize json array into java String using Jackson in a Spring-boot Application. Something like
{"history": ["historyA", "historyB"]} (JSON Request Body) -> String history;
However, the following error message got logged.
Cannot deserialize instance of `java.lang.String` out of START_ARRAY token
My Controller is similar to
#RestController
public class PatientController {
#PostMapping
public void create(#RequestBody #Valid Patient patient) {
mapper.create(patient);
}
}
My POJO is similar to:
#Data
#NoArgsConstructor
public class Patient {
#JsonDeserialize(contentUsing = PatientHistoryDeserializer.class, contentAs = List.class)
private String history;
My Json Deserializer is similar to:
public class PatientHistoryDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
List<String> histories = new LinkedList<>();
if (p.getCurrentToken() == JsonToken.START_ARRAY) {
while (p.getCurrentToken() != JsonToken.END_ARRAY) {
String history = p.getValueAsString();
if(history.contains("#"))
throw new ClientError(HttpServletResponse.SC_BAD_REQUEST, "invalid...");
histories.add(history);
}
}
return String.join("#", histories);
}
}
Is my goal achievable ? Or any suggestions on how to convert as I wanted ?
This can be done like this
public class PatientHistoryDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
if (jsonParser.currentToken() == JsonToken.START_ARRAY) {
List<String> histories = new ArrayList<>();
jsonParser.nextToken();
while (jsonParser.hasCurrentToken() && jsonParser.currentToken() != JsonToken.END_ARRAY) {
histories.add(jsonParser.getValueAsString());
jsonParser.nextToken();
}
return String.join("#", histories);
}
return null;
}
}
And usage would like
#JsonDeserialize(using = PatientHistoryDeserializer.class)
String histories;
The purpose of contentUsing and contentAs are a bit different than the use case here. let's take the following example.
class Histories {
Map<String, String> content;
}
and JSON is something like this
{"content": { "key" : ["A","B"]}}
and you want to deserialize this into a map having (key = "A#B")
there are two ways to do it, write custom deserializer or use contentUsing attribute to specify how your values should be deserialized
#JsonDeserialize(contentUsing = PatientHistoryDeserializer.class)
Map<String, String> content;
Similarly, you can use other annotation attributes like keyUsing for keys for maps.
I'm trying to custom deserialize incoming json to the field, but #JsonDeserialize is not working wheh I put it above the setter of the field I want to deserialize.
public class MyEntity {
#JsonProperty("identifier")
private String identifier;
#JsonDeserialize(using = IdentifierDeserializer.class)
public void setIdentifier(String identifier)
{
this.identifier = identifier;
}
}
....
public class IdentifierDeserializer
extends JsonDeserializer<String>
{
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException
{
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
final JsonNode entity = node.get("entity");
return entity.get("id").asText();
}
}
Im doing this via _restTemplate httpMessageConverter flow, just standar API calls, and no, it does not throw any exceptions, it just not invoked, because I this this with debug and my breakpoints left untouched.
HttpEntity<T> httpEntity = new HttpEntity<>(entity, httpHeaders());
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON_UTF8));
_restTemplate.getMessageConverters().add(jsonConverter);
return _restTemplate.exchange(url, method, httpEntity, MyEntity.class).getBody();
But #JsonDeserialize does work when I put it above the class. So what's the problem guys? Thanks.
I'm using the Flickr API. When calling the flickr.test.login method, the default JSON result is:
{
"user": {
"id": "21207597#N07",
"username": {
"_content": "jamalfanaian"
}
},
"stat": "ok"
}
I'd like to parse this response into a Java object:
public class FlickrAccount {
private String id;
private String username;
// ... getter & setter ...
}
The JSON properties should be mapped like this:
"user" -> "id" ==> FlickrAccount.id
"user" -> "username" -> "_content" ==> FlickrAccount.username
Unfortunately, I'm not able to find a nice, elegant way to do this using Annotations. My approach so far is, to read the JSON String into a Map<String, Object> and get the values from there.
Map<String, Object> value = new ObjectMapper().readValue(response.getStream(),
new TypeReference<HashMap<String, Object>>() {
});
#SuppressWarnings( "unchecked" )
Map<String, Object> user = (Map<String, Object>) value.get("user");
String id = (String) user.get("id");
#SuppressWarnings( "unchecked" )
String username = (String) ((Map<String, Object>) user.get("username")).get("_content");
FlickrAccount account = new FlickrAccount();
account.setId(id);
account.setUsername(username);
But I think, this is the most non-elegant way, ever. Is there any simple way, either using Annotations or a custom Deserializer?
This would be very obvious for me, but of course it doesn't work:
public class FlickrAccount {
#JsonProperty( "user.id" ) private String id;
#JsonProperty( "user.username._content" ) private String username;
// ... getter and setter ...
}
You can write custom deserializer for this class. It could look like this:
class FlickrAccountJsonDeserializer extends JsonDeserializer<FlickrAccount> {
#Override
public FlickrAccount deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Root root = jp.readValueAs(Root.class);
FlickrAccount account = new FlickrAccount();
if (root != null && root.user != null) {
account.setId(root.user.id);
if (root.user.username != null) {
account.setUsername(root.user.username.content);
}
}
return account;
}
private static class Root {
public User user;
public String stat;
}
private static class User {
public String id;
public UserName username;
}
private static class UserName {
#JsonProperty("_content")
public String content;
}
}
After that, you have to define a deserializer for your class. You can do this as follows:
#JsonDeserialize(using = FlickrAccountJsonDeserializer.class)
class FlickrAccount {
...
}
Since I don't want to implement a custom class (Username) just to map the username, I went with a little bit more elegant, but still quite ugly approach:
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(in);
JsonNode user = node.get("user");
FlickrAccount account = new FlickrAccount();
account.setId(user.get("id").asText());
account.setUsername(user.get("username").get("_content").asText());
It's still not as elegant as I hoped, but at least I got rid of all the ugly casting.
Another advantage of this solution is, that my domain class (FlickrAccount) is not polluted with any Jackson annotations.
Based on #MichaĆ Ziober's answer, I decided to use the - in my opinion - most straight forward solution. Using a #JsonDeserialize annotation with a custom deserializer:
#JsonDeserialize( using = FlickrAccountDeserializer.class )
public class FlickrAccount {
...
}
But the deserializer does not use any internal classes, just the JsonNode as above:
class FlickrAccountDeserializer extends JsonDeserializer<FlickrAccount> {
#Override
public FlickrAccount deserialize(JsonParser jp, DeserializationContext ctxt) throws
IOException, JsonProcessingException {
FlickrAccount account = new FlickrAccount();
JsonNode node = jp.readValueAsTree();
JsonNode user = node.get("user");
account.setId(user.get("id").asText());
account.setUsername(user.get("username").get("_content").asText());
return account;
}
}
You can also use SimpleModule.
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override public JsonDeserializer<?> modifyDeserializer(
DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass() == YourClass.class) {
return new YourClassDeserializer(deserializer);
}
return deserializer;
}});
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
objectMapper.readValue(json, classType);
I made it this way:
public class FlickrAccount {
private String id;
#JsonDeserialize(converter = ContentConverter.class)
private String username;
private static class ContentConverter extends StdConverter<Map<String, String>, String> {
#Override
public String convert(Map<String, String> content) {
return content.get("_content"));
}
}
}
You have to make Username a class within FlickrAccount and give it a _content field