I'm using gson to bind some json to a pojo. When I don't use OSGI, everything binds perfectly, so I am feeling like a class type is getting ignored completely due to some classloader issue because the nested collection is null after parsing.
I have an abstract generic class that does the binding within a separate bundle:
public <T> T deserialize(String jsonString, Class<T> clazz) {
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.create();
return gson.fromJson(jsonString, clazz);
}
This approach works without OSGI, but when I use OSGI, it only binds the top-level elements that exist within the T class, but not the nested inner class.
To better illustrate "top-level" elements, the title and description are deserialized into the POJO correctly, but theThings is null. Do I need to somehow embed that nested subtype into the generic?
This is the class signature that contains the deserialize method.
public abstract class MyAbstractClass<T>
{
"title": "my awesome title",
"description": "all the awesome things",
"theThings": [
{
"thing": "coolThing1",
"association-type": "thing"
},
{
"thing": "coolThing2",
"association-type": "thing"
}
]
}
POJO Class that JSON Binds To:
package things;
import java.io.Serializable;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class ThingsPOJO implements Serializable
{
#SerializedName("title")
#Expose
public String title = "";
#SerializedName("description")
#Expose
public String description = "";
#SerializedName("theThings")
#Expose
public List<TheThing> theThings = null;
private class TheThing implements Serializable
{
#SerializedName("thing")
#Expose
public String thing = "";
#SerializedName("association-type")
#Expose
public String associationType = "";
}
}
Related
I have a Java record with one field only:
public record AggregateId(UUID id) {}
And a class with the AggregateId field (other fields removed for readability)
public class Aggregate {
public final AggregateId aggregateId;
#JsonCreator
public Aggregate(
#JsonProperty("aggregateId") AggregateId aggregateId
) {
this.aggregateId = aggregateId;
}
}
The implementation above serialize and deserialize JSON with given example:
ObjectMapper objectMapper = new ObjectMapper();
String content = """
{
"aggregateId": {
"id": "3f61aede-83dd-4049-a6ff-337887b6b807"
}
}
""";
Aggregate aggregate = objectMapper.readValue(content, Aggregate.class);
System.out.println(objectMapper.writeValueAsString(aggregate));
How could I change Jackson config to replace JSON by that:
{
"aggregateId": "3f61aede-83dd-4049-a6ff-337887b6b807"
}
without giving up a separate class for AggregateId and access through fields, without getters?
I tried #JsonUnwrapper annotation, but this caused throws
Exception in thread "X" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Invalid type definition for type `X`:
Cannot define Creator parameter as `#JsonUnwrapped`: combination not yet supported at [Source: (String)"{
"aggregateId": "3f61aede-83dd-4049-a6ff-337887b6b807"
}"
or
Exception in thread "X" com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot define Creator property "aggregateId" as `#JsonUnwrapped`:
combination not yet supported at [Source: (String)"{
"aggregateId": "3f61aede-83dd-4049-a6ff-337887b6b807"
}"
Jackson version: 2.13.1
dependencies {
compile "com.fasterxml.jackson.core:jackson-annotations:2.13.1"
compile "com.fasterxml.jackson.core:jackson-databind:2.13.1"
}
Of course, it's possible with a custom serializer/deserializer, but I'm looking for an easier solution because I have many different classes with a similar issue.
The combination of #JsonUnwrapped and #JsonCreator is not supported yet, so we can generate a solution like this:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.UUID;
public class AggregateTest {
static record AggregateId(#JsonProperty("aggregateId") UUID id) {}
static class Aggregate {
#JsonUnwrapped
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public final AggregateId _aggregateId;
public final String otherField;
#JsonCreator
public Aggregate(#JsonProperty("aggregateId") UUID aggregateId,
#JsonProperty("otherField") String otherField) {
this._aggregateId = new AggregateId(aggregateId);
this.otherField = otherField;
}
}
public static void main(String[] args) throws JsonProcessingException {
String rawJson =
"{\"aggregateId\": \"1f61aede-83dd-4049-a6ff-337887b6b807\"," +
"\"otherField\": \"İsmail Y.\"}";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
Aggregate aggregate = objectMapper
.readValue(rawJson, Aggregate.class);
System.out.println(objectMapper
.writeValueAsString(aggregate));
}
}
Here we briefly get rid of the #JsonUnwrapped field.
We get the UUID with the name aggregateId and create an AggregateId record.
Detailed explanations about it:
https://github.com/FasterXML/jackson-databind/issues/1467
https://github.com/FasterXML/jackson-databind/issues/1497
I am calling an endpoint in which it returns an object. In this object it contains some fields and also a field of another type of object.
E.g.
class ResponseObject{
private final boolean success;
private final String message;
private final DifferentType different;
}
I am calling the endpoint via RestTemplate:
private LogonResponseMessage isMemberAuthenticated(UserCredentialDomain userCredentialDomain)
{
RestTemplate restTemplate = new RestTemplate();
return restTemplate.getForObject(
"http://abc.local:8145/xyz/member/authenticate/{memberLoginName}/{password}", ResponseObject.class,
userCredentialDomain.getUsername(), userCredentialDomain.getPassword());
}
So my consuming app is giving this error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `fgh.thg.member.DifferentTypeABC` (no Creators, like default constructor, exist): cannot deserialize from Object v
alue (no delegate- or property-based Creator)
I am aware that it's telling me to put a default constructor in the DifferentTypeABC class in order for jackson to deserialise it but i can't do that easily as I'll need to update the dependency on apps that the DifferentTypeABC is in across several different repos.
So i was wondering if there is a way of configuring RestTemplate or jackson on the consuming app so that it ignores attempting to deserialise objects if it doesn't contain a default constructor? To be honest, I am pretty much interested in the success and message fields on the response object.
Another way to solve the problem is by enhancing DifferentType in this module using Jackson creator Mixin. Below is an example:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(mapper.getVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
mapper.addMixIn(DifferentType.class, DifferentTypeMixin.class);
String raw = "{\"message\": \"ok\", \"differentType\": {\"name\": \"foo bar\"}}";
ResponseObject object = mapper.readValue(raw, ResponseObject.class);
System.out.println(mapper.writeValueAsString(object));
}
#Data
static class ResponseObject {
private String message;
private DifferentType differentType;
}
static class DifferentType {
private String name;
public DifferentType(String name) {
this.name = name;
}
}
public static abstract class DifferentTypeMixin {
#JsonCreator
DifferentTypeMixin(#JsonProperty("name") String name) {
}
}
}
I've JSON message coming in from rabbitmq and has the following format:
{
messageId: 123,
content: {
id: "P123456",
status: false,
error: {
description: "Something has gone wrong",
id: 'E400'
}
}
}
As you can see, the message has a few nested objects within them.
When this message comes in, I will serialise it using Jackson. Right now, however, I have to create multiple classes just for one single message.
In the example message above, I have to create 3 classes just for serialising and transforming it into a class MainMessage, like so:
public class MainMessage {
private int messageId;
private MessageContentObject content;
// getters/setters...
}
public class MessageContentObject {
private String id;
private boolean status;
private MessageErrorObject error;
// getters/setters...
}
public class MessageErrorObject {
private String description;
private string id;
// getters/setters...
}
This feels very cumbersome because in some of the messages, the nesting can be pretty deep and I will have to create a lot of classes just for the purpose of having the JSON payload transformed into the MainMessage class object. The MessageContentObject and MessageErrorObject are mostly redundant because I will never use the classes directly anywhere else in the code. I would still the values in them through MainMessage though, for example:
#RabbitListener
public void consumeMessage(MainMessage msg) {
System.out.println(msg.getContent().getError().getDescription());
}
I'm using Spring with Spring Boot.
Is this really the only way I can do when it comes to dealing with nested JSON payloads?
First, when the message comes in you Deserialize it.
Now, if you don't want to create the whole data structure to look like your incoming JSON, you can go for a Map like this
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Collections;
import java.util.Map;
public class Message {
private final Map<String, Object> details;
#JsonCreator // Deserialize the JSON using this creator
public Message(final Map<String, Object> details) {
super();
this.details = details;
}
#JsonAnyGetter // Serialize this class using the data from the map
public Map<String, Object> getDetails() {
return Collections.unmodifiableMap(details);
}
}
In this way, you won't need to change your Message class every time your incoming JSON changes.
However, this approach is useful only when you'll not be manipulating the data too much.
if you don't want to create the whole data structure or you only need a small portion of the response recieved at a time you can use Jackson JsonNode
import com.fasterxml.jackson.databind.JsonNode;
public class Message {
private JsonNode messageDetails;
/**
* Constructor used to transform your object into jsonNode.
*
* #param messageDetailsResponse
*/
public Message(Object messageDetailsResponse) {
this.messageDetails = JsonUtils.getNode(messageDetailsResponse);
}
//Simply create getters for accessing the data you want when you want it
public String getId() {
return messageDetails.findValue("messageId").asText();
}
//You can also use it later to map a portion of response to an model class
public Content getContent(){
return JsonUtils.fromJsonNode(messageDetails.get("content"),Content.class)
}
}
The Code for JsonUtils
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
#Slf4j
final public class JsonUtils {
/**
* The Object Mapper constant to deal with JSON to/from conversion activities.
*/
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public static JsonNode getNode(Object anyObject) {
try {
return OBJECT_MAPPER.readTree(OBJECT_MAPPER.writeValueAsBytes(anyObject));
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
public static <T> T fromJsonNode(final JsonNode node, final Class<T> clazz)
throws JsonProcessingException {
return OBJECT_MAPPER.treeToValue(node, clazz);
}
Some suggestions:
Consider using Lombok to reduce the code you need to write (getters setters etc.) - will make creating classes less of a problem.
Consider using static inner classes - will mean you need less files.
If you still prefer to not write your classes down, you can deserealise to a String and traverse it using Gson or similar libraries.
I am not able to unmarshall a JSON key which can hold either a string value or an another JSON Object using Jackson Library.
Ex:- Below are the two possible values.
1)
"ProviderData": {
"INVALID": "HEX",
"#text": "Sample"
}
2)
"ProviderData": "1C"
Could someone please verify and suggest me on this issue.
You can write custom deserialiser and handle these both cases or write two constructors for ProviderData POJO class and properly use JsonCreator and JsonCreator annotations. See below example:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(jsonFile, Response.class));
}
}
class Response {
#JsonProperty("ProviderData")
private ProviderData data;
// getters, setters, toString
}
class ProviderData {
private static final String INVALID_NAME = "INVALID";
private static final String TEXT_NAME = "#text";
#JsonProperty(INVALID_NAME)
private final String invalid;
#JsonProperty(TEXT_NAME)
private final String text;
#JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public ProviderData(String invalid) {
this(invalid, null);
}
#JsonCreator
public ProviderData(#JsonProperty(INVALID_NAME) String invalid, #JsonProperty(TEXT_NAME) String text) {
this.invalid = invalid;
this.text = text;
}
// getters, toString
}
For this JSON payload:
{
"ProviderData": {
"INVALID": "HEX",
"#text": "Sample"
}
}
Above example prints:
Response{data=ProviderData{invalid='HEX', text='Sample'}}
And for String primitive JSON payload:
{
"ProviderData": "1C"
}
Above example prints:
Response{data=ProviderData{invalid='1C', text='null'}}
As you can see, JSON Object is mapped properly using 2-arg constructor and String primitive is mapped using 1-arg constructor and we assume that this value means invalid key from JSON Object example.
See also:
Custom JSON Deserialization with Jackson.
sequentially deserialize using Jackson.
Deserialize strings and objects using jackson annotations in java.
you could deserialize to JsonNode and then extract the contents individually, or deserialize to an Object and use instanceof to determine if it's a Map or another type, or use a custom deserializer to unpack the data into a custom object that handles both cases.
I'm using Gson to deserialize some json string (actually it's jwt) passed in by http header. The json contains:
[
{"authority":"a1"},
{"authority":"a2"},
{"authority":"a3"},
.
.
.
{"authority":"a4"},
]
in JsonElement.
And I'd like the above part to be deserialized into the field (in some class):
Set<GrantedAuthority> authorities
Where GrantedAuthority is an interface from Spring, it has an implementation SimpleGrantedAuthority. SimpleGrantedAuthority has a constructor that takes a string:
public SimpleGrantedAuthority(String au) {this.au = au}
I need Gson to know the implementation class of interface GrantedAuthority in order to deserialize the json. I was trying:
public class GrantedAuthorityInstanceCreator implements InstanceCreator<GrantedAuthority> {
#Override
public GrantedAuthority createInstance(Type type) {
// no such constructor
GrantedAuthority ga = new SimpleGrantedAuthority();
return ga;
}
}
But since SimpleGrantedAuthority has no no-arg constructor, I need to provide an argument to the constructor. How can I achieve this?
InstanceCreator won't work. According to gson-user-guide. you have 3 options:
Option 1: Use Gson's parser API (low-level streaming parser or the DOM parser JsonParser) to parse the array elements and then use Gson.fromJson() on each of the array elements.This is the preferred approach. Here is an example that demonstrates how to do this.
Option 2: Register a type adapter for Collection.class that looks at each of the array members and maps them to appropriate objects. The disadvantage of this approach is that it will screw up deserialization of other collection types in Gson.
Option 3: Register a type adapter for MyCollectionMemberType and use fromJson with Collection
This approach is practical only if the array appears as a top-level element or if you can change the field type holding the collection to be of type Collection.
You said
And I'd like the above part to be deserialized into the field (in some class):
So, assume the "some class" is MyClass, and authorities is one field of MyClass for your json string (and there's other fields in MyClass). Below is an example code using method "Option 3":
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.Set;
public class Test2 {
public static void main(String[] args) throws Exception {
String json = "{ authorities: [\n" +
" {\"authority\":\"a1\"},\n" +
" {\"authority\":\"a2\"},\n" +
" {\"authority\":\"a3\"},\n" +
" {\"authority\":\"a4\"}\n" +
"]}";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeHierarchyAdapter(GrantedAuthority.class, new GrantedAuthorityTypeAdaptor());
Gson gson = gsonBuilder.create();
MyClass obj1 = gson.fromJson(json, MyClass.class);
for (GrantedAuthority au : obj1.authorities) {
SimpleGrantedAuthority sgau = (SimpleGrantedAuthority) au;
System.out.println(sgau.authority);
}
}
}
class MyClass {
Set<GrantedAuthority> authorities;
// other fields
}
interface GrantedAuthority {
}
class SimpleGrantedAuthority implements GrantedAuthority {
final String authority;
public SimpleGrantedAuthority(String au) {
this.authority = au;
}
}
class GrantedAuthorityTypeAdaptor extends TypeAdapter<GrantedAuthority> {
#Override
public void write(JsonWriter out, GrantedAuthority value) throws IOException {
new Gson().getAdapter(SimpleGrantedAuthority.class).write(out, (SimpleGrantedAuthority) value);
}
#Override
public GrantedAuthority read(JsonReader in) throws IOException {
return new Gson().getAdapter(SimpleGrantedAuthority.class).read(in);
}
}
The approach is to use SimpleGrantedAuthorityAdaptor as an adaptor for GrantedAuthority.
In order not to mess up other code, the GsonBuilder should be used only here. You should create a new GsonBuilder in your other code.