JsonMappingException: Unexpected token (START_OBJECT) - java

I'm having problem with the user of Codehaus Jackson. I have an object with the next attributes and mapper declaration:
public class AuthenticatedPrincipal implements Serializable, Principal {
#JsonIgnore
private final static ObjectMapper mapper = new ObjectMapper().enable(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY).enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)
.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL).setVisibility(JsonMethod.FIELD, JsonAutoDetect.Visibility.ANY);
private String name;
private Collection<String> roles;
private Collection<String> groups;
private boolean adminPrincipal;
...
#JsonIgnore
public String serialize() {
try {
return mapper.writeValueAsString(this);
} catch (IOException e) {
throw new RuntimeException("Unable to serialize Principal:" + toString(), e);
}
}
#JsonIgnore
public static AuthenticatedPrincipal deserialize(String json) {
try {
return mapper.readValue(json, AuthenticatedPrincipal.class);
} catch (IOException e) {
throw new RuntimeException("Unable to serialize Principal:" + json, e);
}
}
}
That is used from another class:
public class AuthRequest {
#Transient
private AuthenticatedPrincipal principal;
#PreUpdate
#PrePersist
public void encodePrincipal() {
if (principal != null) {
this.encodedPrincipal = principal.serialize();
}
}
#PostLoad
#PostPersist
#PostUpdate
public void decodePrincipal() {
if (StringUtils.isNotBlank(encodedPrincipal)) {
this.principal = AuthenticatedPrincipal.deserialize(encodedPrincipal);
}
}
}
When I execute the funtionality that generate a String like this:
Principal:{"adminPrincipal":false,"displayName":"sdfas","groupAware":false,"name":"sdfas"}
When the method AuthenticatedPrincipal.deserialize(encodedPrincipal); is called parsing a Json parameter but that method fail with this error:
org.codehaus.jackson.map.JsonMappingException: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class com.trent.app.lib.principal.AuthenticatedPrincipal
at [Source: java.io.StringReader#40fa255; line: 1, column: 1]
Can anyone help me?

Principal:{"adminPrincipal":false,"displayName":"sdfas","groupAware":false,"name":"sdfas"}
Is not valid JSON. It needs to look like this
{"adminPrincipal":false,"displayName":"sdfas","groupAware":false,"name":"sdfas"}
(without the Principal: at the start)

Related

Deserialize a JSON payload to object base on JSON integer property

I have below classes:
public class Result<T> {
public int code;
public Object meta;
public T data;
}
public class User {
public int id;
public String name;
}
public class Error {
public String field;
public String message;
}
I want to deserialize a JSON payload based on code field. If code >= 10, return Result<ArrayList<Error>>, otherwise return Result<User>
Currently, I map JSON to Result<Object> first, then check the code field. Based on that value I make second map to desired object.
ObjectMapper mapper = new ObjectMapper();
Result<Object> tempResult = mapper.readValue(json, new TypeReference<Result<Object>>() {});
if (tempResult.code < 10) {
Result<User> result = mapper.readValue(json, new TypeReference<Result<User>>() {});
return result;
} else {
Result<ArrayList<Error>> result = mapper.readValue(json, new TypeReference<Result<ArrayList<Error>>>() {});
return result;
}
Is there an elegant way to do this without deserializing it 2 times?
You need to implement custom TypeIdResolver:
class UserTypeIdResolverBase extends TypeIdResolverBase {
#Override
public String idFromValue(Object value) {
throw new IllegalStateException("Not implemented!");
}
#Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
throw new IllegalStateException("Not implemented!");
}
#Override
public JsonTypeInfo.Id getMechanism() {
return JsonTypeInfo.Id.CUSTOM;
}
#Override
public JavaType typeFromId(DatabindContext context, String id) {
if (Integer.parseInt(id) < 10) {
return context.getTypeFactory().constructType(new TypeReference<Result<User>>() {});
}
return context.getTypeFactory().constructType(new TypeReference<Result<List<Error>>>() {});
}
}
and declare it for a Result class:
#JsonTypeInfo(property = "code", use = JsonTypeInfo.Id.CUSTOM, visible = true)
#JsonTypeIdResolver(UserTypeIdResolverBase.class)
class Result<T>

Automatic deserialization of String to Object with Jackson

Context
Say you have:
public class Dto {
private String name;
private String List<Custom> customs;
// getters and setters...
}
and
public class Custom {
private String something;
private String else;
// getters and setters...
}
Your Spring MVC RestController receives a list of Dto:
#PostMapping
public String create(#RequestBody #Valid List<Dto> dtos) {
return myService.process(features);
}
Input
However, you know that the client-side service which will send data to your controller will send something like this:
[
{
"name": "Bob",
"customs": [
"{\n \"something\": \"yes\",\n \"else\": \"no\"\n }"
]
}
]
Notice how the List<Custom> actually ends up being received as a List<String>. Please assume this cannot be changed on the client-side and we have to deal with it on the server-side.
Question
Is there a Jackson annotation which would automagically take the input String and try to serialize it into a Custom class?
Attempts
A few things that didn't work, including:
#JsonSerialize(using = ToStringSerializer.class)
private List<Custom> customs;
along with
public Custom(String json) {
try {
new ObjectMapper().readerFor(Custom.class).readValue(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
As it is, we have had to change the customs type to List<String> and add a utility method which converts a String into a Custom using an ObjectMapper. This is rather dissatisfying.
You need to implement custom deserialiser or converter which would be used to convert given payload to required type. One trick, you could use is to create new ObjectMapper and use it for internal deserialisation.
Example usage:
class CustomConverter extends StdConverter<String, Custom> {
private final ObjectMapper mapper = new ObjectMapper();
#Override
public Custom convert(String value) {
try {
return mapper.readValue(value, Custom.class);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(value);
}
}
}
class Dto {
private String name;
#JsonDeserialize(contentConverter = CustomConverter.class)
private List<Custom> customs;
}
You need to create a custom Deserializer.
public class CustomDeserializer extends StdDeserializer<Custom> {
public CustomDeserializer() {
this(null);
}
public CustomDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Custom deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("id")).numberValue();
String name = node.get("name").asText();
...
return new Custom(id, name, ...);
}
}
and register the deserializer on the Custom class:
#JsonDeserialize(using = CustomDeserializer.class)
public class Custom {
...
}

Intercept request to create RequestBody

I have a base CreateRequest class which I inherit in a couple of children, each responsible for some custom validations:
Base:
public class CreateEventRequest {
#NotEmpty
private String name;
#NotNull
#JsonProperty
private Boolean isPrivate;
}
Child:
public class CreateRegularEventRequest extends CreateEventRequest {
#NotNull
#Future
private LocalDateTime startTime;
#NotNull
#Future
private LocalDateTime endTime;
public LocalDateTime getStartTime() {
return startTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
}
In order to take advantage of the validations, I've tried this:
#PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<CreateEventResponse> createEvent(HttpEntity<String> httpEntity,
#AuthenticationPrincipal SecuredUser user) {
try {
CreateEventRequest eventRequest = eventRequestFactory.getEventRequestString(httpEntity.getBody());
Set<ConstraintViolation<CreateEventRequest>> violations = validator.validate(eventRequest);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
return new ResponseEntity<>(this.service.createEvent(eventRequest, user), HttpStatus.CREATED);
} catch (ConstraintViolationException e) {
throw e;
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
My factory is:
public CreateEventRequest getEventRequestString(String json) {
try {
String eventTypeRaw = new ObjectMapper().readTree(json)
.get("event_type").asText();
if (!StringUtils.isEmpty(eventTypeRaw)) {
EventType eventType = EventType.valueOf(eventTypeRaw);
if (EventType.REGULAR.equals(eventType)) {
return objectMapper.readValue(json, CreateRegularEventRequest.class);
} else if (EventType.RECURRING.equals(eventType)) {
return objectMapper.readValue(json, CreateRecurringEventRequest.class);
}
}
return null;
} catch (JsonProcessingException e) {
return null;
}
}
This seems really hacky to me and do not scale for future Parent-Child relation, my question is, is there a way to intercept this request to create this child classes and pass it to the controller or some built-in validations to handle this scenario?
Thanks!
Did yo try:
public ResponseEntity<CreateEventResponse> createEvent( #Valid #RequestBody CreateRegularEventRequest reqBody ...) {
..
all the mapping and validation are done automatically

JPA not updating column with Converter class

I'm using a Converter class to store a complex class as JSON text in mySQL. When I add a new entity, the Converter class works as intended. However, when I update the entity, the data in the complex class is not updated in the database but it's updated in memory. Other attributes such as Lat and Long are updated. The breakpoint I placed at the convertToDatabaseColumn method and it did not trigger on update.
Object Class
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String city;
private String state;
private String country;
#Enumerated(EnumType.STRING)
private StatusType status;
private String street;
private double latitude;
private double longitude;
#Convert(converter=ProjectPropertyConverter.class)
private ProjectProperty property;
}
public class ProjectProperty {
private String description;
private List<String> projectImgs;
private Boolean hasImages;
}
Property Converter Class
#Converter (autoApply=true)
public class ProjectPropertyConverter implements AttributeConverter<ProjectProperty, String> {
#Override
public String convertToDatabaseColumn(ProjectProperty prop) {
try {
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(prop);
return jsonString;
} catch (Exception e) {
System.out.print(e.toString());
return null;
}
}
#Override
public ProjectProperty convertToEntityAttribute(String jsonValue) {
try {
ObjectMapper mapper = new ObjectMapper();
ProjectProperty p = mapper.readValue(jsonValue, ProjectProperty.class);
if(p.getProjectImgs().isEmpty())
{
p.setHasImages(Boolean.FALSE);
}
else
{
p.setHasImages(Boolean.TRUE);
}
return p;
} catch (Exception e) {
System.out.print(e.toString());
return null;
}
}
}
Method to Update Database
public void modifyEntity(Object entity, String query, HashMap params) {
try {
tx.begin();
em.flush();
tx.commit();
} catch (Exception e) {
e.toString();
}
}
I came here looking for same answers. Turns out the problem is JPA doesn't know that your object is dirty. This was solved by implementing equals()/hashcode() methods on this complex objects. In your example, implement equals and hashcode for ProjectProperty
Once that is done, JPA is able to identify via these methods that the underlying object is dirty and converts and persists.

Jackson cannot deserialize enum as object even if I add customized deserializer

I want to use Jackson JSON to serialize/deserialize a class containing an enum object. My class is:
class Bar {
#JsonProperty("rateType")
#JsonDeserialize(using = ReturnedRateTypeDeserializer.class)
private ReturnedRateType rateType;
public ReturnedRateType getRateType() {
return rateType;
}
public void setRateType(ReturnedRateType rateType) {
this.rateType = rateType;
}
}
The enum class ReturnedRateType is defined as:
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ReturnedRateType {
AA("AA"),
BB("BB"),
CC("CC");
#JsonProperty("value")
private String value;
ReturnedRateType(String value) {
this.value = value;
}
#JsonCreator
public static ReturnedRateType fromValue(final String value) {
if (value != null) {
for (ReturnedRateType type : ReturnedRateType.values()) {
if (value.equalsIgnoreCase(type.value)) {
return type;
}
}
}
return null;
}
}
As you see, I added #JsonFormat annotation to tell Jackson to serialize this enum as POJO, and added #JsonCreator annotation to get a static factory method from given string to enum object. Since Jackson can only serialize but can't deserialize from object representation to enum, I added the following customized deserializer for the enum ReturnedRateType:
public class ReturnedRateTypeDeserializer extends JsonDeserializer<ReturnedRateType> {
#Override
public ReturnedRateType deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ReturnedRateType type = ReturnedRateType.fromValue(jp.getValueAsString());
if(type != null)
return type;
throw new JsonMappingException("invalid value for ReturnedRateType");
}
}
But when I tested deserialization from a JSON string to enum, I got the error. The JSON string is:
{"rateType": {"value": "AA"}}
My test code is:
#Test
public void RateTypeToEnum() {
String json = "{\"rateType\": {\"value\": \"AA\"}}";
System.out.println(json);
ObjectMapper mapper = new ObjectMapper();
Bar bar = null;
try {
bar = mapper.readValue(json, Bar.class);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(bar.getRateType());
}
I expect to see the output should be AA. But jp.getValueAsString() in my customized deserializer ReturnedRateTypeDeserializer is null during the execution:
ReturnedRateType type = ReturnedRateType.fromValue(jp.getValueAsString()); //jp.getValueAsString() is null here!
Thus it returns error. So what is wrong here?
According to the Jackson 2.5.X documentation on the JsonFormat annotation the Shape.Object does not work for the enum deserialisation:
Enums: Shapes JsonFormat.Shape.STRING and JsonFormat.Shape.NUMBER can
be used to change between numeric (index) and textual (name or
toString()); but it is also possible to use JsonFormat.Shape.OBJECT
to serialize (but not deserialize).
I'd make the JsonCreator static method accept a JsonNode and read the string value from it.
Note that this would work since 2.5.X. In early versions you would need to write a custom deserialiser. Here is an example:
public class JacksonEnumObjectShape {
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
#JsonDeserialize(using = ReturnedRateTypeDeserializer.class)
public enum ReturnedRateType {
AA("AA"),
BB("BB"),
CC("CC");
#JsonProperty("value")
private String value;
ReturnedRateType(String value) {
this.value = value;
}
#JsonCreator
public static ReturnedRateType fromValue(final JsonNode jsonNode) {
for (ReturnedRateType type : ReturnedRateType.values()) {
if (type.value.equals(jsonNode.get("value").asText())) {
return type;
}
}
return null;
}
}
// can be avoided since 2.5
public static class ReturnedRateTypeDeserializer extends JsonDeserializer<ReturnedRateType> {
#Override
public ReturnedRateType deserialize(
final JsonParser jp,
final DeserializationContext ctxt) throws IOException {
final JsonNode jsonNode = jp.readValueAsTree();
return ReturnedRateType.fromValue(jsonNode);
}
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
final String json = mapper.writeValueAsString(ReturnedRateType.AA);
System.out.println(json);
System.out.println(mapper.readValue(json, ReturnedRateType.class));
}
}
Output:
{"value":"AA"}
AA

Categories