I have this JAXB-annotated class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement
public class SomeClass {
#XmlAttribute
public String value;
}
Based on this class, a JSON document was produced using jersey-json (via com.sun.jersey.api.json.JSONJAXBContext):
{
"#value":"someValue"
}
Note that jersey decided to use #value as tag name (and not simply value), probably in order to honour the #XmlAttribute annotation.
Now the task is to read this json document using jackson-json and produce an instance of the JAXB annotated class:
ObjectMapper mapper = new ObjectMapper();
JaxbAnnotationIntrospector introspector = new JaxbAnnotationIntrospector(mapper.getTypeFactory());
DeserializationConfig deserConfig = mapper.getDeserializationConfig().with(introspector);
mapper.setConfig(deserConfig);
SerializationConfig serConfig = mapper.getSerializationConfig().with(introspector);
mapper.setConfig(serConfig);
mapper.readValue(jsonFile, SomeClass.class);
This fails with the following exception:
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "#value" (class test.jersey.vs.jackson.SomeClass), not marked as ignorable (one known property: "value"])
at [Source: C:\Users\AppData\Local\Temp\junit5080915042527904512\json.txt; line: 1, column: 12] (through reference chain: test.jersey.vs.jackson.SomeClass["#value"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:62)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:834)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1093)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1477)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1455)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:282)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2740)
My question now is whether there is a way to tell jackson to associate the #value tag from the JSON document with the value field of the class.
I tried specifying a PropertyNamingStrategy, but that was never called during deserialization.
PropertyNamingStrategy pns = new PropertyNamingStrategy() {
#Override
public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) {
if (field.hasAnnotation(XmlAttribute.class)) {
if (defaultName.startsWith("#")) {
return defaultName.substring(1);
}
}
return super.nameForField(config, field, defaultName);
}
};
DeserializationConfig deserConfig = mapper.getDeserializationConfig().with(introspector).with(pns);
mapper.setConfig(deserConfig);
Related
I am fetching some data via a REST service, but I am getting this error when deserializing the response :
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "ResultSet Output" (class com.HolderCollectionWrapper), not marked as ignorable (one known property: "holders"]) at [Source: java.io.ByteArrayInputStream#74efa7bd; line: 1, column: 22] (through reference chain: com.HolderCollectionWrapper["ResultSet Output"])
This is my code :
response = restTemplate.exchange(requestUrl, HttpMethod.GET, request, HolderCollectionWrapper.class);
public class HolderCollectionWrapper {
#SerializedName("ResultSet Output")
private List<Holder> holders;
public List<Holder> getHolders() {
return holders;
}
public void setHolders(List<Holder> holders) {
this.holders = holders;
}
}
This is the JSON I am getting :
{
"ResultSet Output": [
{...}, {...}, {...}
]
}
Despite the #SerializedName("ResultSet Output"), it's not working, why ?
#SerializedName is a gson annotation and you are using jackson library for serialization.
The jackson annotation for field name is #JsonProperty
Try:
#JsonProperty("ResultSet Output")
private List<Holder> holders;
This happens because the SerializedName("ResultSet Output") gson annotation indicates that the holders will be serialized with the ResultSet Output name like the json example you post; to deserialize it with jackson you have to use the JsonProperty annotation, specifying the ResultSet Output name applied on the setter to avoid possible conflicts with the gson library used for serialization:
public class HolderCollectionWrapper {
#SerializedName("ResultSet Output")
private List<Holder> holders;
public List<Holder> getHolders() {
return holders;
}
#JsonProperty("ResultSet Output")
public void setHolders(List<Holder> holders) {
this.holders = holders;
}
}
I'm trying to deserialize an array of MyObject (which uses the builder pattern via Lombok with #Jacksonized) from a csv String containing a non-standard representation of a map as one of the columns.
MyObject:
#JsonPropertyOrder({
"strField",
"mapField",
})
#Getter
#Jacksonized
#Builder(toBuilder = true)
#EqualsAndHashCode
#ToString
public class MyObject {
private final String strField;
#Builder.Default
private final Map<String, Float> mapField = new HashMap<>();
}
Example csv with non-standard mapField representation:
strField,mapField
abc,"key1=2.0;key2=3.0"
I'm using a mixin to try to achieve this without rewriting the entire object:
#JsonPropertyOrder({
"strField",
"mapField",
})
public abstract class MyObjectDeserializerMixin {
#JsonDeserialize(using = StringToMapDeserializer.class)
private Map<String, Float> mapField;
}
..which, as you can see above, points to a custom deserializer:
public class StringToMapDeserializer extends JsonDeserializer<Map<String, Float>> {
#Override
public Map<String, Float> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String csvFormattedMap = jsonParser.getText().trim();
return Arrays.stream(csvFormattedMap.split(";"))
.map(keyValue -> keyValue.split("="))
.collect(Collectors.toMap(keyValue -> keyValue[0], keyValue -> Float.parseFloat(keyValue[1])));
}
}
And, to wrap it all up, I'm configuring and using my CsvMapper like so:
CsvMapper csvMapper = new CsvMapper();
csvMapper.addMixIn(MyObject.class, MyObjectDeserializerMixin.class);
CsvSchema csvSchema = csvMapper
.schemaFor(MyObject.class)
.withHeader();
ObjectReader csvReader = csvMapper.readerFor(MyObject.class).with(csvSchema);
List<MyObject> myObjects = csvReader.<MyObject>readValues(theCsvString).readAll();
However, I'm getting the following exception:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
construct instance of java.util.LinkedHashMap (although at least one
Creator exists): no String-argument constructor/factory method to
deserialize from String value ('key1=2.0;key2=3.0') at [Source:
(StringReader); line: 2, column: 53] (through reference chain:
myPackage.MyObject$MyObjectBuilder["mapField"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1432)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1062)
at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:371)
at com.fasterxml.jackson.databind.deser.ValueInstantiator.createFromString(ValueInstantiator.java:258)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:357)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:29)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeSetAndReturn(MethodProperty.java:158)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.vanillaDeserialize(BuilderBasedDeserializer.java:269)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.deserialize(BuilderBasedDeserializer.java:193)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1719)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1261)
...
The stack trace appears to be trying to use a BuilderBasedDeserializer which tries to use a MapDeserializer.java for the map so it doesn't appear to be aware of my custom deserializer. I've used a very similar workflow with a custom serializer to write the same csv, so I'm confused as to why this doesn't work. What is the next step to troubleshooting this?
When using a builder to deserialize, Jackson only considers the annotations on the builder class, not those on the actual class to deserialize. Lombok's #Jacksonized helps you by automatically copying all relevant annotations to the builder class and its setter methods.
However, Lombok can only do that with annotations statically present on the class. Any dynamic annotation coming from a mixin cannot be copied, because Lombok doesn't know about them.
You could put #JsonDeserialize onto the mapField of the actual class, so that Lombok is able to copy it to the builder. But that obviously runs contrary to the purpose of mixins.
Luckily, there is a better way. You can also add the mixin to the builder class as follows:
csvMapper.addMixIn(MyObjectBuilder.class, MyObjectDeserializerMixin.class);
Strictly speaking, you do not need the mixin on MyObject any more. But if you also serialize and there are annotations relevant to serialization in the mixin, you should add the mixin to both.
However, in your case, that's not sufficient, as you are using #Builder.Default. With that annotation, Lombok creates a field named mapField$value in the builder. Jackson will not match the field mapField (and its annotation) from your mixin to that field, as they are named differently.
You can work around that by defining and annotating the setter method in your mixin:
public abstract class MyObjectDeserializerMixin {
#JsonDeserialize(using = StringToMapDeserializer.class)
public abstract void mapField(Map<String, Float> mapField);
}
You can use the actual return type of the builder here, but void is also sufficient. As #JsonDeserialize is only for deserialization purposes, you can safely remove the mapField and its annotation from the mixin class.
Tested with Lombok 1.18.20 and Jackson 2.12.2.
I couldn't find where I was wrong for the following array json string. I am not sure whether I am mapping right. I hope a friend will help me.
Thanks...
JSON string value is like below;
[
{
"PaymentRequest": {
"RequestGuid": 123
...
}
},{
"PaymentRequest": {
"RequestGuid": 456
...
}
}
]
Object definition is like below;
#JsonRootName(value = "PaymentRequest")
#JsonIgnoreProperties(ignoreUnknown = true)
public class PaymentRequest{
#JsonProperty("RequestGuid")
String requestGuid;
...
}
My wrapper class is like below;
public class MyWrapper{
PaymentRequest paymentRequest;
//setter getter
}
My implementation is like below.
ObjectMapper mapper = new ObjectMapper();
List<MyWrapper> users = mapper.readValue(jsonString, new TypeReference<List<MyWrapper>>() {});
Result:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "PaymentRequest" (class vpos.dto.MyWrapper), not marked as ignorable (one known property: "paymentRequest"])
at [Source: (StringReader); line: 3, column: 24] (through reference chain: java.util.ArrayList[0]->vpos.dto.MyWrapper["PaymentRequest"])
Problem is that property in json is called PaymentRequest and your field is paymentRequest with lower p at the begining. You can add annotation #JsonProperty("PaymentRequest") to your field or change property naming strategy like this:
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE);
I need to configure Jackson in a specific way which I'll describe below.
Requirements
Annotated fields are serialized with only their id:
If the field is a normal object, serialize its id
If the field is a collection of objects, serialize an array of id
Annotated fields get their property names serialized differently:
If the field is a normal object, add "_id" suffix to property name
If the field is a collection of objects, add "_ids" suffix to property name
For the annotation I was thinking something like a custom #JsonId, ideally with an optional value to override the name just like #JsonProperty does
The id property should be defined by the user, either using:
The already existing Jackson's #JsonIdentityInfo
Or by creating another class or field annotation
Or by deciding which annotation to inspect for id property discoverability (useful for JPA scenarios, for example)
Objects should be serialized with a wrapped root value
Camel case naming should be converted to lower case with underscores
All of this should be deserializable (by constructing an instance with just the id setted)
An example
Considering these POJO's:
//Inform Jackson which property is the id
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id"
)
public abstract class BaseResource{
protected Long id;
//getters and setters
}
public class Resource extends BaseResource{
private String name;
#JsonId
private SubResource subResource;
#JsonId
private List<SubResource> subResources;
//getters and setters
}
public class SubResource extends BaseResource{
private String value;
//getters and setters
}
A possible serialization of a Resource instance could be:
{
"resource":{
"id": 1,
"name": "bla",
"sub_resource_id": 2,
"sub_resource_ids": [
1,
2,
3
]
}
}
So far...
Requirement #5 can be accomplished by configuring ObjectMapper in the following way:
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
And then using #JsonRootName("example_root_name_here") in my POJO's.
Requirement #6 can be accomplished by configuring ObjectMapper in the following way:
objectMapper.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
As you can see there are still lots of requirements to fulfill. For those wondering why I need such a configuration, it's because I'm developing a REST webservice for ember.js (more specifically Ember Data).
You would appreciate very much if you could help with any of the requirements.
Thanks!
Most (all?) of your requirements can be accomplished through the use of a contextual serializer. Taking one answer from ContextualDeserializer for mapping JSON to different types of maps with Jackson and Jackson's wiki (http://wiki.fasterxml.com/JacksonFeatureContextualHandlers) I was able to come up with the following.
You need to start with the #JsonId annotation, which is the key indicating a property needs to only use the Id property.
import com.fasterxml.jackson.annotation.*;
import java.lang.annotation.*;
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotation // important so that it will get included!
public #interface JsonId {
}
Next is the actual ContextualSerializer, which does the heavy lifting.
import com.fasterxml.jackson.databind.ser.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.core.*;
import java.io.*;
public class ContextualJsonIdSerializer
extends JsonSerializer<BaseResource>
implements ContextualSerializer/*<BaseResource>*/
{
private ObjectMapper mapper;
private boolean useJsonId;
public ContextualJsonIdSerializer(ObjectMapper mapper) { this(mapper, false); }
public ContextualJsonIdSerializer(ObjectMapper mapper, boolean useJsonId) {
this.mapper = mapper;
this.useJsonId = useJsonId;
}
#Override
public void serialize(BaseResource br, JsonGenerator jgen, SerializerProvider provider) throws IOException
{
if ( useJsonId ) {
jgen.writeString(br.getId().toString());
} else {
mapper.writeValue(jgen, br);
}
}
#Override
public JsonSerializer<BaseResource> createContextual(SerializerProvider config, BeanProperty property)
throws JsonMappingException
{
// First find annotation used for getter or field:
System.out.println("Finding annotations for "+property);
if ( null == property ) {
return new ContextualJsonIdSerializer(mapper, false);
}
JsonId ann = property.getAnnotation(JsonId.class);
if (ann == null) { // but if missing, default one from class
ann = property.getContextAnnotation(JsonId.class);
}
if (ann == null ) {//|| ann.length() == 0) {
return this;//new ContextualJsonIdSerializer(false);
}
return new ContextualJsonIdSerializer(mapper, true);
}
}
This class looks at BaseResource properties and inspects them to see if the #JsonId annotation is present. If it is then only the Id property is used, otherwise a passed in ObjectMapper is used to serialize the value. This is important because if you try to use the mapper that is (basically) in the context of the ContextualSerializer then you will get a stack overflow since it will eventually call these methods over and over.
You're resource should look something like the following. I used the #JsonProperty annotation instead of wrapping the functionality in the ContextualSerializer because it seemed silly to reinvent the wheel.
import java.util.*;
import com.fasterxml.jackson.annotation.*;
public class Resource extends BaseResource{
private String name;
#JsonProperty("sub_resource_id")
#JsonId
private SubResource subResource;
#JsonProperty("sub_resource_ids")
#JsonId
private List<SubResource> subResources;
//getters and setters
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public SubResource getSubResource() {return subResource;}
public void setSubResource(SubResource subResource) {this.subResource = subResource;}
public List<SubResource> getSubResources() {return subResources;}
public void setSubResources(List<SubResource> subResources) {this.subResources = subResources;}
}
Finally the method that performs the serialization just creates an additional ObjectMapper and registers a module in the original ObjectMapper.
// Create the original ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
// Create a clone of the original ObjectMapper
ObjectMapper objectMapper2 = new ObjectMapper();
objectMapper2.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
objectMapper2.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper2.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
// Create a module that references the Contextual Serializer
SimpleModule module = new SimpleModule("JsonId", new Version(1, 0, 0, null));
// All references to SubResource should be run through this serializer
module.addSerializer(SubResource.class, new ContextualJsonIdSerializer(objectMapper2));
objectMapper.registerModule(module);
// Now just use the original objectMapper to serialize
I'm trying to override property name specified in #JsonProperty during serialization, but get both old and new named properties in the resulting json.
Entity:
class Bean {
#JsonProperty("p")
String prop;
#JsonCreator
Bean(#JsonProperty("p") String prop) {
this.prop = prop;
}
}
Serializing code:
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy() {
#Override
public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) {
return "prop";
}
});
System.out.println(mapper.writeValueAsString(new Bean("test")));
Results in:
{"p":"test","prop":"test"}
Accrding to Jackson's code, this happens because constructor parameters are also annotated with #JsonProperty. I'm using Jackson 1.9.5.
Is there a way to disable constructor parameters and get {"prop":"test"} ?
Thanks for help in advance!
There is no way to directly disable annotations, but if you want to block their effects, you can sub-class JacksonAnnotationIntrospector, and override logic used for finding #JsonProperty annotation (or #JsonCreator).