Deserializing "Unwrapped" Json Arrays to POJO - jackson

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.

Related

Spring Data Rest - Custom LinkRelationProvider is never used

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?

UnrecognizedPropertyException: Unrecognized field - jackson 2.9

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);

Cannot deserialize instance of MyEnum out of START_OBJECT token

Stuck with the problem of MyEnum enum deserialization from JSON to POJO and cannot figure out what I do wrong. So basically I try to retrieve some data calling particular microservice endpoint that returns the following json:
{
"id": "9cabf3e9-965d-4407-b62b-c57dd6006419",
"myEnums": [
{
"context": "SOME_FOO_CONTEXT_1",
"feature": "SOME_BAR_FEATURE_1",
"name": "SOME_FOO_BAR_1"
},
{
"context": "SOME_FOO_CONTEXT_2",
"feature": "SOME_BAR_FEATURE_2",
"name": "SOME_FOO_BAR_2"
}
],
"name": "Some name",
"updatedBy": null,
"updated": "2019-05-16T00:11:19.279Z"
}
This is the method that calls another microservice endpoint, deserialize response body to POJO and return result as Set:
private Mono<Set<MyEnum>> fetchMyEnums(UUID someId) {
return webClient.get().uri("/v1/something/{id}", someId)
.retrieve()
.bodyToMono(MyClass.class)
.flatMapIterable(MyClass::getMyEnums)
.collect(toSet());
}
The class that used for JSON deserialization:
#lombok.Value
static class MyClass {
List<MyEnum> myEnums;
}
Enum that I actually cannot deserialize:
#Getter
#RequiredArgsConstructor
#AllArgsConstructor
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum {
SOME_FOO_BAR_1(SOME_FOO_CONTEXT_1, SOME_BAR_FEATURE_1),
SOME_FOO_BAR_2(SOME_FOO_CONTEXT_2, SOME_BAR_FEATURE_2);
private final FooEnum context;
private final BarEnum feature;
private String name;
#JsonProperty
public String getName() {
return super.name();
}
}
During deserialization I receive the following exception:
org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize instance of `com.learn.common.security.model.MyEnum` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.learn.common.security.model.MyEnum` out of START_OBJECT token
at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.learn.common.security.service.MyEnumService$MyClass["myEnums"]->java.util.ArrayList[0])
Where I did mistake?
So spending few more hours to clarify what's the problem with deserialization i figure out that there is no automatic deserialization for Enum whose Shape.Object.
But I found workaround how to deserialize MyEnum object from json(you need define static method marked it as JsonCreator and define what input parameter you expect to catch from object defining JsonProperty with fieldName):
#Getter
#RequiredArgsConstructor
#AllArgsConstructor
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum {
SOME_FOO_BAR_1(SOME_FOO_CONTEXT_1, SOME_BAR_FEATURE_1),
SOME_FOO_BAR_2(SOME_FOO_CONTEXT_2, SOME_BAR_FEATURE_2);
private final FooEnum context;
private final BarEnum feature;
private String name;
#JsonProperty
public String getName() {
return super.name();
}
#JsonCreator
public static MyEnum fromJson(#JsonProperty("name") String name) {
return valueOf(name);
}
}
For sake of completeness: You can add the #JsonCreator annotation either to a constructor or to a factory method.
Constructor:
#JsonCreator
public MyEnum(#JsonProperty("name") String name) {
this.name = name;
}
Factory method:
#JsonCreator
public static MyEnum fromJson(#JsonProperty("name") String name) {
return valueOf(name);
}
Multiple parameters:
If your enum type contains multiple properties, add them to the signature with #JsonProperty annotation.
#JsonCreator
public MyEnum(#JsonProperty("id") String id, #JsonProperty("name") String name) {
this.id = id;
this.name = name;
}
When using a factory method, creation from JSON might fail, if you have defined multiple properties. You may get this error message:
Unsuitable method [...] decorated with #JsonCreator (for Enum type [...])
Some versions of Jackson cannot handle this case. Use constructor method as a workaround, if your enum type contains more than one property.

Duplicate _links key in CrudRepository REST response

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.

deserializing JSON vb.net response google maps api

need some help here.
I'm not getting deserialize this json in vb.net.
I need the values lat : -21.4105261 and lng : -42.1956855.
{
"results" : [
{
"address_components" : [
{
"long_name" : "28460-000",
"short_name" : "28460-000",
"types" : [ "postal_code" ]
}
],
"formatted_address" : "Rua Francisco Cardoso, 25 - Morro do Demétrio, Miracema - RJ, 28460-000, Brazil",
"geometry" : {
"bounds" : {
"northeast" : {
"lat" : -21.4105261,
"lng" : -42.1956855
},
"southwest" : {
"lat" : -21.4105429,
"lng" : -42.1956892
}
},
"location" : {
"lat" : -21.4105429,
"lng" : -42.1956892
},
"location_type" : "RANGE_INTERPOLATED",
"viewport" : {
"northeast" : {
"lat" : -21.4091855197085,
"lng" : -42.1943383697085
},
"southwest" : {
"lat" : -21.4118834802915,
"lng" : -42.1970363302915
}
}
},
"place_id" : "ElBSdWEgRnJhbmNpc2NvIENhcmRvc28sIDI1IC0gTW9ycm8gZG8gRGVtw6l0cmlvLCBNaXJhY2VtYSAtIFJKLCAyODQ2MC0wMDAsIEJyYXNpbA",
"types" : [ "street_address" ]
}
],
"status" : "OK"
}
Can anyone help?
This is the first time I use Json so I'm not experienced with it.
Ps: I have the json.net library installed.
You can use json.net and deserialize the json.
1/ Create the model
Public Class GooglCitiesResult
Public Property city As String
Public Property country As String
Public Property country_code As String
End Class
Public Class GoogleGeoCodeResponse
Public Property status As String
Public Property results As results()
End Class
Public Class results
Public Property formatted_address As String
Public Property geometry As geometry
Public Property types As String()
Public Property address_components As address_component()
End Class
Public Class geometry
Public Property location_type As String
Public Property location As location
End Class
Public Class location
Public Property lat As String
Public Property lng As String
End Class
Public Class address_component
Public Property long_name As String
Public Property short_name As String
Public Property types As String()
End Class
2/ call it in your controller
GoogleUrl = "http://maps.google.com/maps/api/geocode/json?address=Paris,France&sensor=false&language=en"
Dim client As WebClient = New WebClient()
client.Encoding = System.Text.Encoding.UTF8
Dim result = Await client.DownloadStringTaskAsync(GoogleUrl)
Dim jsonGoogle As GoogleGeoCodeResponse = Newtonsoft.Json.JsonConvert.DeserializeObject(Of GoogleGeoCodeResponse)(result)
Dim GoogleLatitude As String = jsonGoogle.results(0).geometry.location.lat
Dim GoogleLongitude As String = jsonGoogle.results(0).geometry.location.lng
First you need to create a custom class to represent the object you are looking to deserialize.. then you would do something like...
Dim jss = New JavaScriptSerializer
Dim resp As List(Of cls_custom_class) = jss.Deserialize(Of List(Of cls_custom_class))(json)
IF you built the class properly, the Deserialize function should be able to map the JSON to your custom object and then you will have full access to whatever field/values you need.