Problem:
The CrudRepository return a faulty JSON Response with duplicate _links key
{"_links" : { },
"_embedded" : {
"skills" : [ {
"name" : "REST",
"_links" : { }, <----------- Empty Links
"_embedded" : { },
"_links" : { <-------------- Usefull Links
"self" : {
"href" : "http://localhost:8081/api/skills/1",
"templated" : false
}
}
} ] } }
Used Classes:
Repository:
import org.springframework.data.repository.CrudRepository;
public interface SkillRepository extends CrudRepository<Skill, Long> {}
Entity:
#Entity
#Getter
#Setter
public class Skill {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
private String name;
}
If I include jackson-databind the problem occurs.
<artifactId>jackson-databind</artifactId>
<version>2.6.0</version>
The latest version of spring-hateoas includes jackson-databind 2.4.6.
In jackson-databind 2.6.0 JsonSerialize.Inclusion became deprecated; JsonInclude should be used instead. The Mixin classes from spring-hateoas (ResourcesMixin, ResourceSupportMixin & LinkMixin) use JsonSerialize.Inclusion which apparently is ignored.
Solution: use jackson-databind 2.5.4 (or lower) until the jackson version is updated in spring-hateoas.
Related
I am creating a service based on spring-boot-starter-parent 2.6.1 and use spring data rest to expose my JPA repository:
public interface PointRepo extends CrudRepository<Point<?>, String> {}
The Point type has subtypes Quantity and Switch, so a GET /points currently returns something like:
{
"_embedded" : {
"switches" : [ {
...
"_links" : {
"self" : {
"href" : "http://localhost:8080/switch/office_light"
},
...
}
}],
"quantities" : [ {
...
"_links" : {
"self" : {
"href" : "http://localhost:8080/quantity/office_temp"
},
...
}
}
Since I am not planning to expose endpoints /switches and /quantities, I want all Points to be in _embedded.points and the self hrefs to point to /points, too. I figured, I need a custom LinkRelationProvider so I created this:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class PointRelProvider extends EvoInflectorLinkRelationProvider {
#Override
public LinkRelation getCollectionResourceRelFor(final Class<?> type) {
return super.getCollectionResourceRelFor(Point.class);
}
#Override
public LinkRelation getItemResourceRelFor(Class<?> type) {
return super.getItemResourceRelFor(Point.class);
}
#Override
public boolean supports(LookupContext delimiter) {
return Point.class.isAssignableFrom(delimiter.getType());
}
}
I found out the bean gets created, but it has no effect on the output whatsoever. I put breakpoints into each method put none of them ever gets called. Any ideas why that might be the case?
I am doing a simple conversion using Jackson:
response = mapper.readValue(responseStr, PrinterStatus.class);
The code is throwing this exception:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "OutputParameters" (class com.xerox.PrinterStatus),
not marked as ignorable (one known property: "outputParameters"]) at ....
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:823)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1153)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1589)
The Json I would like to convert is very simple:
{
"OutputParameters": {
"#xmlns": "http://xmlns.xerox.com/apps/rest/",
"#xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
"GETPRINTERSTATUS": {
"GETPRINTERSTATUS_ITEM": [{
"STATUS": "True",
"MESSAGE": " "
}]
}
}
}
This is the PrinterStatus class, it has the field "OutputParameters"
So I am not sure what is Jackson yelling about.
public class PrinterStatus {
private OutputParameters outputParameters;
public OutputParameters getOutputParameters() {
return outputParameters;
}
public void setOutputParameters(OutputParameters outputParameters) {
this.outputParameters = outputParameters;
}
...
Basically JSON keys are case sensitive. Accordingly OutputParameters doesn't equal to outputParameters.
So you have to choose:
rename the field in Java class (and getters / setters too) to OutputParameters
rename JSON property key to outputParameters
If you using Jackson 2.9 or above just simply annotate field like this:
public class PrinterStatus {
#JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
private OutputParameters outputParameters;
public OutputParameters getOutputParameters() {
return outputParameters;
}
public void setOutputParameters(OutputParameters outputParameters) {
this.outputParameters = outputParameters;
}
...
}
Set property name explicitly
public class PrinterStatus {
#JsonProperty("OutputParameters")
private OutputParameters outputParameters;
...
}
Enable case insesitive feature globally
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
Half of my problem is not knowing the exact terms to search for an answer. I believe I'm dealing with what's called a "flat" or "unwrapped" json array with the underlying "members" node in that this would work if there was a "member:" element for each of the "members" underlying property nodes, but there isn't.
I receive json (I can't control format) that looks like this:
{
"id" : "1",
"context" :
{
"id" : "123,
"title" : "My Title"
},
"members": [
{
"prop1" : { },
"prop2" : "123",
"propArray1" : [ "Value1", "Value2" ],
"prop3" : "xyz",
"prop4" : "123"
},
{
"prop1" : { },
"prop2" : "456",
"propArray1" : [ "Value1", "Value2" ],
"prop3" : "abc",
"prop4" : "456"
}
] }
My POJO (minus the simple gets/sets):
#JsonAutoDetect
public class MyPojo {
#JsonProperty
private String id;
#JsonProperty
private Context context;
#JsonProperty("members")
private List<Member> members = new ArrayList<>();
#JsonAutoDetect
public class Context {
public Context() {}
#JsonProperty
private String id;
#JsonProperty
private String title;
}
#JsonAutoDetect
public class Member {
public Member() {}
#JsonProperty
private String prop1;
#JsonProperty
private String prop2;
#JsonProperty
private List<String> propArray1 = new ArrayList<>();
#JsonProperty
private String prop3;
#JsonProperty
private String prop4;
#JsonProperty
private String prop5;
}
public List<Member> getMembers() {
return members;
}
public void setMembers(List<Member> members) {
this.members = members;
}
}
I've tried GSON:
new Gson().fromJson(jsonEntity, MyPojo.class);
returns:
java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT
I've tried Jackson:
new ObjectMapper().readValue(jsonEntity, MyPojo.class);
throws:
org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type [simple type, class MyPojo$Member]: can not instantiate from JSON object (need to add/enable type information?)
at [Source: java.io.StringReader#5c6e5b53; line: 10, column: 3] (through reference chain: MyPojo["members"])
I don't think "add/enable" type information is a relevant warning and I do have the default constructor.
I've searched dozens of json deserialization posts and this one seems similar to this but I have to get the entire Json object not just a piece of it...and I tried it just to extract members array and it didn't work.
Cannot deserialize JSON to POJO
there are some issues here:
the JSON testdata is not valid (missing closing " at second id). corrected version:
{
"id" : "1",
"context" :
{
"id" : "123",
"title" : "My Title"
},
"members": [
{
"prop1" : { },
"prop2" : "123",
"propArray1" : [ "Value1", "Value2" ],
"prop3" : "xyz",
"prop4" : "123"
},
{
"prop1" : { },
"prop2" : "456",
"propArray1" : [ "Value1", "Value2" ],
"prop3" : "abc",
"prop4" : "456"
}
] }
second issue: nested classes by default have a reference to the enclosing class. one consequence of this is that they cannot be created without an instance of the enclosing class. Jackson i get this exception:
Cannot construct instance of `foo.MyPojo$Member` (although at least one Creator exists)
fix this by converting the nested classes to static inner classes:
public static class Context {
...
}
third issue: the mapping does not match the JSON.
Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
...
(through reference chain: foo.MyPojo["members"]->java.util.ArrayList[0]->foo.MyPojo$Member["prop1"])
fix this by changing the mapping. e.g:
#JsonAutoDetect
public static class Member {
public Member() {}
#JsonProperty
private Map<String, String> prop1;
...
}
Using this 3 changes its possible to deserialize the JSON using Jackson.
I am trying to come up with an easy to use CDI way to use properties. Based on several blogs this is the (not working) result (sorry for the layout cannot get that right).
1. EEProperties (the provider, seperate jar):
#Singleton
public class EEProperties extends AbstractPropertiesDecorator {
#Inject
public EEProperties(#InjectInEEProperties EnhancedMap properties) {
super(properties);
}
private String[] getKeys(final InjectionPoint ip) {
return (ip.getAnnotated().isAnnotationPresent(Property.class) && ip.getAnnotated().getAnnotation(Property.class).keys().length>0) ?
ip.getAnnotated().getAnnotation(Property.class).keys() :
new String[] {ip.getMember().getName()};
}
#Produces
#Property
public File[] getFileProperties(InjectionPoint ip) {
return getFileProperties(null, getKeys(ip));
}
}
2. A ManagedBean consumer (in war):
#Inject
#Property(keys = {"test"})
private String test;
3. ...that also produces the constructor argument for the provider:
#Produces
#InjectInEEProperties
public EnhancedMap getP() {
EnhancedMap m = new Settings();
m.put("test", "cdi works");
return m;
}
4. annotation for cdi container:
#Qualifier
#Retention(RUNTIME)
#Target({FIELD,ElementType.METHOD,ElementType.PARAMETER})
public #interface InjectInEEProperties {
#Nonbinding String key() default "";
}
5. annotation for consumer:
#Qualifier
#Retention(RUNTIME)
#Target({FIELD,ElementType.METHOD})
public #interface Property {
#Nonbinding String[] keys() default {};
}
6. problem when running this (on payara 5):
Caused by: java.lang.NullPointerException at
org.glassfish.weld.services.JCDIServiceImpl.createManagedObject(JCDIServiceImpl.java:463)
at
org.glassfish.weld.services.JCDIServiceImpl.createManagedObject(JCDIServiceImpl.java:314)
at
com.sun.enterprise.container.common.impl.managedbean.ManagedBeanManagerImpl.createManagedBean(ManagedBeanManagerImpl.java:476)
I've tried a lot of things, but cannot get this to work, including removing the #Produces from the ManagedBean.
Solved the issue by creating a seperate class responsible for providing constructor argument:
#Singleton
public class PP {
#Produces
#InjectInEEProperties
public EnhancedMap getP() {
EnhancedMap m = new Settings();
m.put("test", "cdi works");
return m;
}
}
Assuming I have this objects:
class Person {
String name;
Household getHousehold();
}
class Household {
Set<Address> getAddresses();
String householdId;
}
which would normally be serialized as follows
{
"name": "XXX",
"household": {
"addresses": [...]
}
}
Is there a way to configure Jackson with annotations / mix-ins to obtain this (ie. without using DTO) ?
{
"name": "XXX",
"addresses": [...],
"household": {
"householdId": 123
}
}
You can configure the unwrapping of a specific property by both using mixins and annotations:
1. Mixins
Assuming you define the following mixin:
public abstract class UnwrappedAddresses {
#JsonUnwrapped
public abstract Household getHouseHold();
}
And then add a custom module to your objectMapper which applies the mixin to the Person class as follows:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper .registerModule(new SimpleModule() {
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Person.class, UnwrappedAddresses.class);
}
});
This approach does not change the Household serialization as a single item, but just unwraps a household item when it's encapsulated in a Person object.
2. Annotations
Just add #JsonUnwrapped to your getHouseHold() method.
EDIT: After post changes.
What you want is basically to change the output of the json, which can be done by using the #JsonAnyGetter annotation(which can dynamically add new properties to your pojo).
Your expected result can be achieved by ignoring the household property and unwrapping it with the help of the #JsonAnyGetter.
#JsonIgnoreProperties("houseHold")
public static class Person {
String name;
Household houseHold;
#JsonAnyGetter
public Map<String,Object> properties(){
Map<String,Object> additionalProps=new HashMap<>();
additionalProps.put("addresses", new ArrayList<>(houseHold.getAddresses()));
Map<String,Object> houseHolProps=new HashMap<>();
houseHolProps.put("houseHoldId", houseHold.id);
additionalProps.put("houseHold", houseHolProps);
return additionalProps;
}
..getters&setters omitted
}
Which would after serialization return
{"name":"name",
"houseHold":{"houseHoldId":0},
"addresses":[
{"houseNo":2,"street":"abc"},
{"houseNo":1,"street":"str"}
]
}