I want to restructure an application so that it uses REST instead of an EJB3Factory which wasn't needed in the first place and only makes trouble with the new server.
Basically I have a class Request:
public class Request {
public String name;
public String id;
public List<? extends someObject> list;
// default constructor
...
// non-default constructor
public Request(String name, String id, List<T> list) {
this.name = name;
this.id = id;
this.list = list;
}
The Request gets created and using Gson made into a Json object:
Gson gson = new Gson();
String payload = gson.toJson(Request);
This then gets sent to the REST API on the server. There Jackson deserializes it. I do not have access to the Jackson implementation there and cannot change it to Gson.
What I am basically trying to do now is to get Jackson to use the non-default constructor to deserialize the object. I know I can annotate the non-default constructor like this:
#JsonCreator
public Request(#JsonProperty("name") String name, #JsonProperty("id")
String id, #JsonProperty("list") List<T> list) {
this.name = name;
this.id = id;
this.list = list;
}
The thing is though that the field name of list is set at runtime per reflection and the Gson object that is generated might have it as scenarioName1 for one and scenarioName2 for something else.
I have looked at the different solutions provided here on Stack Overflow but none of them could provide me with a solution for my problem. This seemed most helpful but I cannot under any circumstances use a wrapper property nor can I actually map all possibilities.
Anyone got any idea?
EDIT to include examples:
Example 1:
{"name":"someName","id":"first","someScenarioName":[{...}]}
Example 2:
{"name":"someOtherName","id":"second","differentScenarioName":[{...}]}
Since I'm out of town on business that is the best I can do with right now. It's basically the last field having a different name depending on which scenario was chosen beforehand.
Maybe you can try take a look on Mapper Features. Sincerely I didn't try it yet because I'm at work and so on, but I will send now my example and maybe it can help you:
public class Request {
public String name;
public String id;
public List<? extends T> list;
// default constructor
...
// non-default constructor
public Request(String name, String id, List<T> list) {
this.name = name;
this.id = id;
this.list = list;
}
}
Then to deserialize the object:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.USE_ANNOTATIONS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.readValue(json, Request.class);
My try is because the deserialization by annotation is true by default, but once you don't have a "list" object most of time, it won't find the field there.
Okay, so I figured out what my problem was. There are other lists in the class and those were the trouble. After annotating each of them with #JsonProperty("theirRespectiveName") it worked like a charm... Now I have to annotate about 100 lines of code and solve some more problems.
Related
This question already has answers here:
In Spring-mvc the attribute names in view have to always match the property names in model?
(3 answers)
How to customize parameter names when binding Spring MVC command objects?
(10 answers)
Closed 3 years ago.
So, url requested looks like
localhost:8080/contacts?id=22&name=John&eventId=11
and also I got an object to map request into
public class ContactDTO {
private Long id;
private String name;
private Long eventId;
}
I use a controller method like passing my request params into an object
#GetMapping("/contacts")
public ContactDTO contacts(ContactDTO contact) {
// everything is awesome! contact maps clearly
return contact;
}
The question is how to map like this but have different name
localhost:8080/contacts?id=22&name=John&event_id=11
Setting #JsonAttribute doesn't works because Jackson mapper works only in requestbody.
Maybe I should write custom HandlerMethodArgumentResolver or something like that?
P.S.
I've got a dirty hack (objectMapper is injected, so I can use #JsonAttributes),
But this case fails on array mapping, same mapping with requestbody works fine
#GetMapping("/contacts")
public ContactsDTO contacts(#RequestParam Map<String,String> params) {
ContactDTO contactDTO = objectMapper.convertValue(params,ContactDTO.class);
return contactDTO;
}
Since it is an API design requirement, it should be clearly reflected in the corresponding DTO's and endpoints.
Usually, this kind of requirement stems from a parallel change and implies that the old type queries will be disabled during the contract phase.
You could approach the requirement by adding the required mapping "query-parameter-name-to-property-name" by adding it to the ContactDTO. The simplest way would be just to add an additional setter like below
public class ContactDTO {
private Long id;
private String name;
private Long eventId;
public void setEvent_id(Long eventId) {
this.eventId = eventId;
}
}
If you prefer immutable DTO's, then providing a proper constructor should work as well
#Value
public class ContactDTO {
private Long id;
private String name;
private Long eventId;
public ContactDTO(Long id, String name, String eventId, String event_id) {
this.id = id;
this.name = name;
this.eventId = eventId != null ? eventId : event_id;
}
}
Use something like
#RequestParam(name="event_id", required = true) long eventId
in the parameter list to change the parameter name.
Use #RequestBody insteaf of #requestparam.
I have a JSON String and I want to deserialize it to a Java object with a interface data member. The Java object looks like this:
public class Person {
private String id;
private String name;
private AddressInterface addr;
}
Both the Person and the AddressInterface are third party classes, so I can't make any changes to them.
When I used the following to deserialize the JSON string,
objectMapper.readValue(json_file, Person.class)
I got the following exception. It's because the object mapper doesn't know how to deserialize the AddressInterface field. Can someone let me know how to deserialize the string into an Person object in this case? Many Thanks.
abstract types either need to be mapped to
concrete types, have custom deserializer,
or be instantiated with additional type information
AddressInterface is an interface and is considered abstract. Both classes Foo and Bar could implement AddressInterface, but it would be unable to tell which one the data should be deserialized as.
Random idea which might work:
Put the interface in a wrapper. I'm just guessing since I don't know the library context, but maybe something like this. Also there's probably a few typos in here, but it shows the general idea.
public class AbstractSerializable<T> implements Deserialize {
private final String className;
private T obj;
public AbstractSerializable(T obj) {
this.obj = obj;
this.className = obj.getClass().getCardinalName();
}
#Override
public AbstractSerializable deserialize(ObjectMapper objectMapper) {
String clazz = input.readNext(String.class);
return objectMapper.readNext(Class.forName(clazz));
}
}
Edit: This would probably break if you tried to put a lambda in it.
Edit 2: #Hadi Note is correct that Gson would make some things easier, however it would run into the same issues. I did find this article which explains how to fix it when using Gson. It uses a similar approach to my answer, but they have a much better explanation.
With GSON library you can get rid of the boilerplate codes!
You can use GSON library in the link below!
https://www.tutorialspoint.com/gson/gson_quick_guide.htm
the problem is deserializing AddressInterface property because its an interface and I think objectMapper is trying to initilaize it's default constructer like bellow
addr = new AddressInterface();
you can create an empty concrete class which inherits the AddressInterface and use it instead of AddressInterface
public class Adress implements AddressInterface {
...
}
public class Person {
private String id;
private String name;
private Adress addr;
}
this is my class
public class Response<T> {
private final T data;
private final String error;
I can only change a class. Can jackson serialise it without configuring objectMapper?
EDIT:
I'm using this object as a method result parameter in spring mvc #RestController. And jackson's objectMapper.canSerialize(Response.class) returns false.
EDIT:
I've fixed it this way:
public class Response {
private T data;
private String error;
#JsonIgnore
#JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY
)
private Class<T> type;
...
#JsonIgnore
public Class<T> getType() {
return type;
}
While serializing, I think there will not be an issue as T data is going to hold the object of some real object and libraries like Gson will be able to serialize them properly, but there can be an issue with deserialization due to the fact that it doesn't know in which type it has to be deserialized. For that, you may be required the pass the specific Type so that to tell the library in which type you are trying to deserialize.(Note: I have worked with mostly Gson hence answers are influenced by that)
Refer : https://stackoverflow.com/a/7299718/1093333
I have json with field _id
String json = "{ _id : 1, name : 'Alex', role: 'admin' }"
In my Realm model I use #SerializedName attribute:
public class User extends RealmObject {
#SerializedName("_id")
#PrimaryKey
private int id;
private String name;
private String comment;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
}
If try save the json:
realm.createOrUpdateObjectFromJson(User.class, json)
field _id can't parsed and in database created record with id = 0
In docs using #SerializedName attribute
Gson gson = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}).create();
User user = gson.fromJson(json, User.class);
realm.beginTransaction();
realm.copyToRealmOrUpdate(user);
In this case json = "{ _id : 1, role: 'user' }" just remove user name from database, because default value for String is null.
So, probably I incorrectly using the attribute. How to consider the attribute when dealing with the methods of conservation of json (createOrUpdateObjectFromJson, etc)?
Why writing all these custom serializers when you can make Gson and
Realm work together with just ONE LINE OF CODE?
IMO, setting an exclusive strategy to every model we want to serialize is not the best approach. As you have realized, this approach requires writing a lot of boiler-plate code which is error prone and the worst of all, kills what Gson is about (which is making our lives less painful).
And since we're working-around incompatibilities, why don't you just make sure to pass an unmanged RealmObject to your Gson serializer?
Simplest solution ever (IMO)
Found here. Get a copy in memory of the managed RealmObject and pass it to Gson
new Gson().toJson(realm.copyFromRealm(managedModel));
And that's it! No more code to write!
Other good solutions and explanations are posted here.
You probably need a hybrid solution in order to make this work. The reason is that #SerializedName is a GSON annotation and not one that Realm knows about. So this means you have two choices as you already discovered:
1) Use GSON, which means the end result is an object with null as the default for name.
2) Use createOrUpdateObjectFromJson which means that _id will be ignored because Realm's JSON parser require a 1:1 mapping.
As neither solution will work correctly you can modify them in the following way:
1) With GSON, instead of doing copyToRealmOrUpdate you can manually search for the object and update the role:
realm.beginTransaction();
realm.where(User.class).equalTo("id", user.getId()).findFirst().setRole(user.getRole());
realm.commitTransaction();
2) Using just the pure JSON you can modify to match the expected format:
JSONObject obj = new JSONObject(json);
obj.put("id", obj.getString("_id"));
obj.remove("_id");
Realm has an issue tracking the request for custom mappings, but it has a low priority as that feature is usually better covered by frameworks such as GSON, Jacokson, etc. : https://github.com/realm/realm-java/issues/1470
I'm using Jackson as a tool to declare some objects whose classes I can't annotate (or modify at all). One of the classes has a setter and getter for an untyped list. Here's a sanitized version:
public class Family {
private List members;
public List getMembers() { return members; }
public void setMembers(List members) { this.members = members; }
//...many, many other properties
}
public class Member {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Here's the JSON I'm trying to deserialize:
{ "members" : [ { "name" : "Mark" } ] }
The naive code I would use is this:
ObjectMapper mapper = new ObjectMapper();
Family family = mapper.readValue(json, Family.class);
Member member = (Member) family.getMembers().get(0);
System.out.println(member.getName());
But of course this fails, as Jackson did not know to create a list of Members instead of its fallback, a list of LinkedHashMaps.
What's the easiest way to instruct Jackson to treat members as a List<Member>? I don't think I want to use a fully custom deserializer for the class, since there are many other properties that Jackson handles fine.
Here's the best I could come up with, using BeanDeserializerModifier:
mapper.setDeserializerProvider(new StdDeserializerProvider()
.withDeserializerModifier(new BeanDeserializerModifier() {
#Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BasicBeanDescription beanDesc, BeanDeserializerBuilder builder) {
if (beanDesc.getBeanClass() == Family.class) {
CollectionType type = CollectionType.construct(ArrayList.class, SimpleType.construct(Member.class));
TypeDeserializer typeDeserializer = type.getTypeHandler();
SettableBeanProperty.MethodProperty membersProperty = (SettableBeanProperty.MethodProperty) builder.removeProperty("members");
builder.addProperty(new SettableBeanProperty.MethodProperty(
"members",
type,
typeDeserializer,
beanDesc.getClassAnnotations(),
(AnnotatedMethod) membersProperty.getMember()
));
}
return builder;
}}));
It works, but seems really low level (and verbose!) for what I'm trying to do. What am I missing here?
Edit
I should note, I'm using Jackson 1.8.2, but could update if there's a compelling reason to.
Mix-in annotations were the critical piece of the puzzle I was missing. Here's a much cleaner way of solving this problem:
ObjectMapper mapper = new ObjectMapper();
mapper.getDeserializationConfig().addMixInAnnotations(Family.class, FamilyMixin.class);
Family family = mapper.readValue(json, Family.class);
Member member = (Member) family.getMembers().get(0);
//...
interface FamilyMixin {
#JsonDeserialize(contentAs = Member.class)
void setMembers(List members);
}
What mix-in annotations let you do is annotate a proxy that is under your control. When that mix-in class is applied to the real class, Jackson behaves as if those annotations annotated the real class's members.
In my case, I use JsonDeserialize.contentAs() to specify the container's content type. But I believe most annotations should be available using this method.