Java jackson deserialize json - Getting symbol "=" instead of ":" - java-8

This is what I have. I'm trying to deserialize a json but it doesn't work:
public class MyClass {
private Object attribute;
#JsonCreator
public MyClass(Object attribute) {
this.attribute = attribute;
}
#JsonProperty("attr")
public Object getAttribute() {
return attribute;
}
}
public void method() {
InputStream eventsStream = this.getClass().getClassLoader()
.getResourceAsStream("fileName.json");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.readValue(eventsStream, MyClass.class);
}
fileName.json
{
"value1": 1,
"value2": [
{
"subValue1": "valueExample"
}
]
}
I'm getting this result with symbol "=" instead of ":":
{value1=1, value2=[{subValue=valueExample}]}
It must be a property or something like that. Any idea? Thanks

Why don't you save the deserialization into a variable?
MyClass myClassObject = objectMapper.readValue(eventsStream, MyClass.class);
if you do this, you will have direct the deserialized object in myClassObject and so you can use it easily.
Another method to deserialize would be to do it using Gson like:
new Gson().fromJson(eventStream.toString(), MyClass.class); //you need the eventStream as a String type
the import is this one:
import com.google.gson.Gson;
Can you please tell us why do you need to print the deserialized value?

Related

Deserialising problem with lombok builder and jackson, cannot construct instance of [...] no String-argument constructor/factory method to deserialize

This is a simplified version of a problem I've been having. Given these classes:
#Value
#Jacksonized
#Builder(builderClassName = "Builder", setterPrefix = "with")
public class Limits {
Limit minimum;
Limit maximum;
}
#Value
#Jacksonized
#Builder(builderClassName = "Builder", setterPrefix = "with")
public class Limit {
#JsonValue
String value;
}
and this code:
Limits limits = Limits.builder()
.withMinimum(Limit.builder().withValue("One-Day").build())
.withMaximum(Limit.builder().withValue("One-Year").build())
.build();
System.out.println(objectMapper.writeValueAsString(limits));
it works as expected and gives me the following output:
{
"minimum": "One-Day",
"maximum": "One-Year"
}
However, when I try to deserialise the same JSON string, as follows:
String json = """
{"minimum":"One-Day","maximum":"One-Year"}
""";
objectMapper.readValue(json, Limits.class);
I get the following error:
Cannot construct instance of `Limit$Builder` (although at least one Creator exists):
no String-argument constructor/factory method to deserialize from String value ('One-Day')
at [Source: (String)"{"minimum":"One-Day","maximum":"One-Year"}"; line: 1, column: 12]
Is there a way to make it work without changing the data model or the JSON?
I tried adding #JsonCreator to the Builder of Limit as follows, but gives the same error
#Value
#Jacksonized
#Builder(builderClassName = "Builder", setterPrefix = "with")
public class Limit {
#JsonValue
String value;
public static final class Builder {
#JsonCreator
public Builder withValue(String value) {
this.value = value;
return this;
}
}
}
Appreciate any input on what I might be missing here.
Jackson does not support #JsonCreator in combination with builders. #JsonCreator basically says: The annotated method constructs a new instance, passing the JSON value as argument to its single parameter. That does not work for builders, because there is no such method that does it both (there are two separate methods, one for receiving a JSON value, and one for constructing the instance).
However, there is a simple workaround in your case without loosing immutability. Just put #JsonCreator on an all-args constructor as follows:
#Value
#AllArgsConstructor(onConstructor_ = {#JsonCreator})
public class Limit {
#JsonValue
String value;
}
You can keep the #Builder if you want to, but there is no need to do so.

How to test SpringMvc controller that returns pageable result with MockMvc?

Using spring-boot 2.2.4.
I have a SpringMvc Controller that returns pageable objects:
#RestController
#RequestMapping("/call-data")
public class CallDataController {
#GetMapping
public Page<CallDataDto> findAll(Pageable page) {
...
Trying to test it with MockMvc:
ObjectMapper mapper = new ObjectMapper();
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/call-data")).andReturn();
Page<CallDataDto> myDtos = mapper.readValue(mvcResult.getResponse().getContentAsString(), TypeUtils.pageTypeRef());
...
public class TypeUtils {
public static <T> TypeReference<RestResponsePage<T>> pageTypeRef() {
return new TypeReference<>() {
};
}
But instead of page with dto objects I get a page with LinkedHashMaps.
So how to get the page with dto objects?
Similar question: ObjectMapper using TypeReference not working when passed type in generic method
You can solve the problem by replacing the type parameter(T) with CallDataDto.
public class TypeUtils {
public static TypeReference<RestResponsePage<CallDataDto>> pageTypeRef() {
return new TypeReference<>() {
};
}
Type parameters(e.g. <T>) don't exist at runtime so you have to replace them with some concrete values so that Jackson can obtain full generics type information.

Spring Boot and Kotlin - How to validate a JSON object

I'm using Spring Boot in Kotlin.
I'm taking in some JSON string, parsing it with ObjectMapper however I want to validate it has everything as in per the model - namely id and s3FilePath are not blank or missing.
So this is the model I want to validate against:
#JsonIgnoreProperties(ignoreUnknown = true)
class MyModel {
var id : String = ""
var s3FilePath : String = ""
}
This is where I use that model:
class FirstMessage {
fun create(newMessage: String) : String {
val objectMapper = ObjectMapper()
val parsedMap : MyModel = objectMapper.readValue(newMessage, MyModel::class.java)
val result = MyModel()
result.id = parsedMap.id
result.s3FilePath = parsedMap.s3FilePath
return objectMapper.writeValueAsString(result)
}
}
And finally I have this test where I want to validate an exception:
#Test
fun incompleteDataReturnsException() {
var input = """{"missing": "parts"}"""
// FirstMessage().create(input) // Will make some assertion here here
}
Any help would be appreciated. I've just started using Spring and its pretty 'intense'.
Thanks.
p.s. If creating that model wrong/there's a better way, please let me know. I'm a little unsure if thats the correct way.
You should use data classes for the models. Also, use kotlin jacksonObjectMapper() instead of ObjectMapper(). Standard ObjectMapper will not work in Kotlin. Or inject ObjectMapper from Spring context. Add "com.fasterxml.jackson.module:jackson-module-kotlin" in your dependencies.
#JsonIgnoreProperties(ignoreUnknown = true)
data class MyModel (
val id : String,
val s3FilePath : String
)
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
class FirstMessage {
fun create(newMessage: String) : String {
val parsedMap : MyModel = jacksonObjectMapper().readValue(newMessage)
return jacksonObjectMapper().writeValueAsString(parsedMap)
}
}
class FirstMessageTest {
#Test
fun incompleteDataReturnsException() {
val input = """{"missing": "parts"}"""
assertThrows (MissingKotlinParameterException::class.java
{FirstMessage().create(input)} // Will make some assertion here here
}
#Test
fun `Should parse`() {
val input = """{"id":"id",
"missing": "parts",
"s3FilePath":"somePath"}"""
FirstMessage().create(input) // Will make some assertion here here
}
}
If I understood your question correct, you just want to check if your required properties are set. So I would suggest checking for that properties after you parsed the string with something like this:
class FirstMessage {
fun create(newMessage: String) : String {
val objectMapper = ObjectMapper()
// validation 1: your input is valid JSON
val parsedMap : MyModel = objectMapper.readValue(newMessage, MyModel::class.java)
// validation 2: check that your properties are set
if(parsedMap.id.isNullOrEmpty() ||
parsedMap.s3FilePath.isNullOrEmpty())
{
throw IllegalArgumentException("Invalid input")
}
val result = MyModel()
result.id = parsedMap.id
result.s3FilePath = parsedMap.s3FilePath
return objectMapper.writeValueAsString(result)
}
}
Depending of the scope, a nicer solution would be a new annotation like #NotEmpty that you set on the properties of your target class that are required and have a generic parser function which validates all the annotated fields on your parsed object and throws a better exception which says exactly which fields are missing.

Polymorphic (de-)serialization to specific type if field type is an interface with Jackson

I would like to de-/serialize based on an interface and I cannot change the classes because they are 3rd party (org.springframework.data.mongodb.core.geo.GeoJson)
POJO:
public class Geofence {
#Id
private String id;
private String topic;
private Date expiresOn;
private Map<String, String> properties;
private GeoJson geometry;
// getters and setters
}
The geometry field could be GeoJsonPolygon or GeoJsonPoint.
I also added this MixIn class:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = GeoJsonPolygon.class, name = "Polygon"),
#JsonSubTypes.Type(value = GeoJsonPoint.class, name = "Point")})
public abstract class GeoJsonMixIn implements GeoJson{
#JsonProperty("type")
public String getType() {
return null;
}
#JsonProperty("coordinates")
public Iterable<?> getCoordinates() {
return null;
}
}
If I try to deserialize an object (via a REST interface) like
{
"expiresOn": "2017-01-01",
"topic": "test",
"properties": {
"radius": 200
},
"geometry": {
"type": "Point",
"coordinates": [
13.349997997283936,
52.51448414445241
]
}
}
I get this exception:
Could not read document: Can not construct instance of org.springframework.data.mongodb.core.geo.GeoJsonPoint: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
On serialization I get:
Failed to instantiate org.springframework.data.mongodb.core.geo.GeoJson using constructor NO_CONSTRUCTOR with arguments
Is this even possible? How can I achieve this?
Everything works, if I don't use polymorhpism - like if I change it to
private GeoJsonPoint geometry;
and my GeoJson is actually a Point. So there apparently IS a suitable constructor etc.
Probably is too late but I had the same exact problem as you and I figured out that you need to add a configuration on Spring to enable GeoJsonModule. So you need to create a Spring configuration class, if you don't have it already, and add this:
#Configuration
public class SpringMongoDbConfig {
#Bean
public Module registerGeoJsonModule(){
return new GeoJsonModule();
}
}
SpringMongoDbConfig is the class I created for editing Spring Configuration, choose what you prefer.
As you can see from the doc GeoJsonModule it's a specific class for serialization and deserialization. All other code with the Abstract class seems ok to me, it's the way to go.

How do you handle deserializing empty string into an Enum?

I am trying to submit a form from Ext JS 4 to a Spring 3 Controller using JSON. I am using Jackson 1.9.8 for the serialization/deserialization using Spring's built-in Jackson JSON support.
I have a status field that is initially null in the Domain object for a new record. When the form is submitted it generates the following json (scaled down to a few fields)
{"id":0,"name":"someName","status":""}
After submitted the following is seen in the server log
"nested exception is org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.blah.domain.StatusEnum from String value '': value not one of the declared Enum instance names"
So it appears that Jackson is expecting a valid Enum value or no value at all including an empty string. How do I fix this whether it is in Ext JS, Jackson or Spring?
I tried to create my own ObjectMapper such as
public class MyObjectMapper extends Object Mapper {
public MyObjectMapper() {
configure(DeserializationConfig.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
}
}
and send this as a property to MappingJacksonMappingView but this didn't work. I also tried sending it in to MappingJacksonHttpMessageConverter but that didn't work. Side question: Which one should I be sending in my own ObjectMapper?
Suggestions?
The other thing you could do is create a specialized deserializer (extends org.codehaus.jackson.map.JsonDeserializer) for your particular enum, that has default values for things that don't match. What I've done is to create an abstract deserializer for enums that takes the class it deserializes, and it speeds this process along when I run into the issue.
public abstract class EnumDeserializer<T extends Enum<T>> extends JsonDeserializer<T> {
private Class<T> enumClass;
public EnumDeserializer(final Class<T> iEnumClass) {
super();
enumClass = iEnumClass;
}
#Override
public T deserialize(final JsonParser jp,
final DeserializationContext ctxt) throws IOException, JsonProcessingException {
final String value = jp.getText();
for (final T enumValue : enumClass.getEnumConstants()) {
if (enumValue.name().equals(value)) {
return enumValue;
}
}
return null;
}
}
That's the generic class, basically just takes an enum class, iterates over the values of the enum and checks the next token to match any name. If they do it returns it otherwise return null;
Then If you have an enum MyEnum you'd make a subclass of EnumDeserializer like this:
public class MyEnumDeserializer extends EnumDeserializer<MyEnum> {
public MyEnumDeserializer() {
super(MyEnum.class);
}
}
Then wherever you declare MyEnum:
#JsonDeserialize(using = MyEnumDeserializer.class)
public enum MyEnum {
...
}
I'm not familiar with Spring, but just in case, it may be easier to handle that on the client side:
Ext.define('My.form.Field', {
extend: 'Ext.form.field.Text',
getSubmitValue: function() {
var me = this,
value;
value = me.getRawValue();
if ( value === '' ) {
return ...;
}
}
});
You can also disallow submitting empty fields by setting their allowBlank property to false.
Ended up adding defaults in the EXT JS Model so there is always a value. Was hoping that I didn't have to this but it's not that big of a deal.
I have the same issue. I am reading a JSON stream with some empty strings. I am not in control of the JSON stream, because it is from a foreign service. And I am always getting the same error message. I tried this here:
mapper.getDeserializationConfig().with(DeserializationConfig.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
But without any effect. Looks like a Bug.

Resources