How to remove a field from POJO - java

public class user {
private String planId;
private String eid;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Collection<String> userIds;
}
I have a pojo like above
and the code which is use for creating the json request object is this :
public UserCollection getUserCollection(final user args) {
Map<String, String> headersMap = new HashMap<>();
ObjectMapper jacksonMapper = new ObjectMapper();
jacksonMapper.disable(MapperFeature.USE_ANNOTATIONS); // this line is creating the userIds field to reflect
//By any code we can remove the userId field from the args object
String responseBody = null;
String responseStatus = null;
String jsonRequestBody = jacksonMapper.writeValueAsString(args);
}
I just want to remove userIds from the args by not removing any of the above code.
Thanks in advance.

I don't know how you should solve this without removing the annotation processing code, you should maybe add a custom serializer. You can read further about the topic at here.
public class HelloWorld {
public static void main(String[] args) throws JsonProcessingException {
User myItem = new User("planId", "eId", List.of("1", "2"));
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(User.class, new UserSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(myItem);
}
#Data
#AllArgsConstructor
public static class User {
private String planId;
private String eId;
private Collection<String> userIds;
}
public static class UserSerializer extends StdSerializer<User> {
public UserSerializer() {
this(null);
}
public UserSerializer(Class<User> t) {
super(t);
}
#Override
public void serialize(User value, JsonGenerator gen, SerializerProvider provider) throws IOException{
gen.writeStartObject();
gen.writeStringField("planId", value.planId);
gen.writeStringField("eId", value.eId);
gen.writeEndObject();
}
}
}
If you had to use it without annotations, and cannot add a custom serializer you should map the User class into a more specific class without the field in question and then serialize that one.

Related

Deserialize multiple json fields into single java property

I want to convert a json into Java class by having custom deserializer.
I'm able to serialize ACC_NUM, NAME and any other fields from json but not sure what can be done to convert MOBILE_NUMBER_1,MOBILE_NUMBER_2 like fields into single JSONArray(See AccountInfo class). There can be many more fields like this and count also is not fixed. Example there can be ADDRESS_1, ADDRESS_2 till ADDRESS_20 and so on and all this fields should go in JSONArray of ADDRESS after deserilization.
I have a Map of Map which holds info like this:
{
"accountInfo": {
"ACC_NUM": "1234567890",
"NAME": "John Cena",
"MOBILE_NUMBER_1": "12376534",
"MOBILE_NUMBER_2": "12376534",
"MOBILE_NUMBER_3": "12376534",
"MOBILE_NUMBER_4": "12376534"
},
"someOther": {
//similer to above
}
}
This info I want to convert to the following class CommonInfo:
public class CommonInfo {
private AccountInfo accountInfo;
//other properties...
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class AccountInfo {
#JsonProperty("ACC_NUM")
private FieldValue<BigInteger> accountNum;
#JsonProperty("NAME")
private FieldValue<String> name;
#JsonProperty("MOBILE_NUMBER")
private FieldValue<JSONArray> mobileNumber;
}
//FieldValue class
public interface FieldValue<T> {
T getInitialValue();
void setInitialValue(T initialValue);
T getValue();
void setValue(T value);
}
#JsonInclude(JsonInclude.Include.ALWAYS)
public class FieldValueImpl<T> implements FieldValue<T> {
protected T initialValue;
protected T value;
//getters, setters, cons..
}
My service code takes json/Map and tries to convert it to CommonInfo class
#Service
public class MyService {
private final ObjectMapper jsonMapper = new ObjectMapper();
#PostConstruct
protected void init() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(FieldValue.class, new FieldValueSerializer());
simpleModule.addDeserializer(FieldValue.class, new FieldValueDeserializer());
jsonMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
jsonMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
jsonMapper.registerModule(simpleModule);
}
public CommonInfo setPojoResult(Map<String, LinkedHashMap<String, String>> contentAsMap) {
return jsonMapper.convertValue(contentAsMap, CommonInfo.class);
}
}
Serializer and Deserializer looks like this:
public class FieldValueDeserializer extends JsonDeserializer<FieldValue<?>> implements ContextualDeserializer {
private JavaType valueType;
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
var deserializer = new FieldValueDeserializer();
if (property == null) {
deserializer.valueType = ctxt.getContextualType().containedType(0);
} else {
var wrapperType = property.getType();
var valueType = wrapperType.containedType(0);
deserializer.valueType = valueType;
}
return deserializer;
}
#Override
public FieldValue<?> deserialize(JsonParser parser, DeserializationContext context) throws IOException {
FieldValueDeserializer deserializer = new FieldValueDeserializer();
deserializer.getKnownPropertyNames();
FieldValue<?> fieldValueImpl = new FieldValueImpl<>();
if (valueType.toString().contains("java.time.LocalDate")) {
JsonNode node = parser.getCodec().readTree(parser);
FieldValue<LocalDate> f1 = new FieldValueImpl<>();
f1.setValue(DateUtils.convertJulianToLocalDate(node.textValue()));
return f1;
} else {
fieldValueImpl.setValue(context.readValue(parser, valueType));
}
return fieldValueImpl;
}
}
//--
public class FieldValueSerializer extends StdSerializer<FieldValue> {
public FieldValueSerializer() {
this(null);
}
public FieldValueSerializer(Class<FieldValue> vc) {
super(vc);
}
#Override
public void serialize(FieldValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(String.valueOf(value.getCurValue()));
}
}

How to tell to Jackson ObjectWriter ignore from class member during write

I'm using Java.
I have the following class:
class MyClass{
#JsonProperty("test")
String test;
String myPrivateTest;
}
I'm using Jackson ObjectWriter to write my JSON object to file/as string as follow:
ObjectWriter writer = objMapper.writer(new DefaultPrettyPrinter());
writer.writeValue(new File(path), myObject);
I want to write to file only test member and not myPrivateTest, How can I do it?
EDIT
I have been tried with:
class MyClass{
#JsonProperty("test")
String test;
#JsonIgnore
String myPrivateTest;
}
And with
#JsonIgnoreProperties({"myPrivateTest"})
class MyClass{
#JsonProperty("test")
String test;
String myPrivateTest;
}
And It is still write myPrivateTest to the file
Using Jackson 2.9.6
Just adding the #JsonProperty to the fields you want is enough.
public class MyClass {
#JsonProperty("test")
String test;
String myPrivateTest;
public static void main(String[] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
MyClass c = new MyClass();
c.test = "HELLO";
c.myPrivateTest = "WORLD";
System.out.println(objectMapper.writeValueAsString(c));
}
}
Output:
{"test":"HELLO"}
String myPrivateTest is correctly ignored
You can ignore specific fields with providing PropertyFilter as below :
#JsonFilter("someFilter")
#Data // lombok
#NoArgsConstructor // lombok
class SomeClass {
private String name;
private int someField;
public SomeClass(String name, int sf){
this.name=name;
this.someField = sf;
}
}
#Test
public void test() throws JsonProcessingException {
SomeClass someClass = new SomeClass("test",1);
ObjectMapper mapper = new ObjectMapper();
PropertyFilter theFilter = new SimpleBeanPropertyFilter() {
#Override
public void serializeAsField
(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
throws Exception {
if (include(writer)) {
// here you can define the properties to ignore
if (!writer.getName().equals("name")) {
writer.serializeAsField(pojo, jgen, provider);
return;
}
} else if (!jgen.canOmitFields()) {
writer.serializeAsOmittedField(pojo, jgen, provider);
}
}
};
FilterProvider filters = new SimpleFilterProvider()
.addFilter("someFilter", theFilter);
String objAsString = mapper
.writer(filters)
.writeValueAsString(someClass);
assertThat(objAsString).doesNotContain("name");
}
Serialized output is :
{"someField":1}

Jackson: Keep references to keys in map values when deserializing

I have the following JSON with a map from user IDs to user details:
{
"users": {
"john": { "firstName": "John", "lastName": "Doe" },
"mark": { "firstName": "Mark", "lastName": "Smith" }
}
}
and I'm using the following code to deserialize the JSON into a Java objects:
class User {
public String userID;
public String firstName;
public String lastName;
}
public class Users {
public Map<String, User> users;
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Reader source = Files.newBufferedReader(Paths.get("test.json"));
Users all = mapper.readValue(source, Users.class);
// ...
}
}
After the deserialization, I want the field User.userID to be set to the corresponding key in the users map.
For example all.users.get("john").userID should be "john".
How can I do that?
Create a custom deserializer for User object and use this for the Map. Here's a full example:
#Test
public void test() throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
Data data = mapper.readValue("{\"users\": {\"John\": {\"id\": 20}, \"Pete\": {\"id\": 30}}}", Data.class);
assertEquals(20, data.users.get("John").id);
assertEquals(30, data.users.get("Pete").id);
assertEquals("John", data.users.get("John").name);
assertEquals("Pete", data.users.get("Pete").name);
}
public static class Data {
#JsonDeserialize(contentUsing = Deser.class)
public Map<String, User> users;
}
public static class User {
public String name;
public int id;
}
public static class Deser extends JsonDeserializer<User> {
#Override
public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String name = ctxt.getParser().getCurrentName();
User user = p.readValueAs(User.class);
user.name = name; // This copies the key name to the user object
return user;
}
}
The simplest solution for the problem is to implement a custom deserializer for the class in which you need the map key (see john16384's answer). This is however cumbersome if you have multiple maps with different value types in your JSON because you'd need one deserializer per type.
In this case, there is a better solution: I would create a custom #JsonMapKey annotation to mark the target properties for the map keys, and then register a generic custom deserializer that processes all occurrences of the annotation. These are the parts you need for this:
Custom #JsonMapKey annotation:
/**
* Annotation used to indicate that the annotated property shall be deserialized to the map key of
* the current object. Requires that the object is a deserialized map value.
*
* Note: This annotation is not a standard Jackson annotation. It will only work if this is
* explicitly enabled in the {#link ObjectMapper}.
*/
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonMapKey {
}
Custom deserializer that processes the #JsonMapKey annotations:
public class JsonMapKeyDeserializer extends DelegatingDeserializer {
private static final long serialVersionUID = 1L;
private BeanDescription beanDescription;
public JsonMapKeyDeserializer(JsonDeserializer<?> delegate, BeanDescription beanDescription) {
super(delegate);
this.beanDescription = beanDescription;
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return new JsonMapKeyDeserializer(newDelegatee, beanDescription);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String mapKey = p.getCurrentName();
Object deserializedObject = super.deserialize(p, ctxt);
// set map key on all fields annotated with #JsonMapKey
for (BeanPropertyDefinition beanProperty : beanDescription.findProperties()) {
AnnotatedField field = beanProperty.getField();
if (field != null && field.getAnnotation(JsonMapKey.class) != null) {
field.setValue(deserializedObject, mapKey);
}
}
return deserializedObject;
}
}
Registration of the custom deserializer in the ObjectMapper:
private static void registerJsonMapKeyAnnotation(ObjectMapper objectMapper) {
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDescription, JsonDeserializer<?> originalDeserializer) {
return new JsonMapKeyDeserializer(originalDeserializer, beanDescription);
}
});
objectMapper.registerModule(module);
}
Then you only need to annotate the field to be used for the map key...
class User {
#JsonMapKey
public String userID;
public String firstName;
public String lastName;
}
... and deserialize your JSON with the prepared ObjectMapper:
Users all = registerJsonMapKeyAnnotation(new ObjectMapper()).readValue(source, Users.class);
First Create the ObjectMapper class object than configure it.
Try following one.
Sample Code
Map<K, V> map;
ObjectMapper mapper = new ObjectMapper();
mapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
map = mapper.readValue(jsonStr, new TypeReference<Map<K, V>>() {});
than you can get the value using Map.

Custom serializer for serializing a List<User> in List<String>

I've a Model object Group
public class Group {
String title;
List<User> members;
String createdBy;
}
I'm using Jackson to serialize this Object. Instead of serializing the whole User object in list "members" I want to serializer just the user.getTitle() field.
Basically I want a HashMap to be something like
{
"title" : "sometitle"
"members" : [user1.getTitle(), user2.getTitle()]
}
I've written a custom serializer for this
public class GroupSerializer extends JsonSerializer<Circle> {
#Override
public void serialize(Group value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
if(value != null) {
gen.writeStartObject();
gen.writeStringField("title", value.getTitle());
gen.writeStringField("createdBy", value.getCreatedBy());
gen.writeFieldName("members");
gen.writeStartArray();
for(User user : value.getMembers()) {
gen.writeString(user.getEmail());
}
gen.writeEndArray();
gen.writeEndObject()
}
}
}
But it's not working. How do I serialize only a field of List instead of whole User Object?
I suggest that you look into Jackson's Converter interface, which seems more suited to the task than creating a custom serializer.
One approach it to create a Converter instance and add it to the ObjectMapper, so that it will be used for the serialization of all User instances.
public class UserConverter extends StdConverter<User, String> {
#Override
public String convert(User user) {
return user.getTitle();
}
}
Register it on your ObjectMapper like this:
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(User.class, new StdDelegatingSerializer(new UserConverter()));
ObjectMapper om = new ObjectMapper().registerModule(simpleModule);
Another approach, in case you don't want to convert all User instances to String, is to annotate selected properties with a converter like this:
public class Group {
String title;
#JsonSerialize(converter = ListUserConverter.class)
List<User> members;
String createdBy;
}
And have a corresponding converter that looks something like this:
public class ListUserConverter extends StdConverter<List<User>, List<String>> {
#Override
public List<String> convert(List<User> users) {
return users.stream().map(User::getTitle).collect(Collectors.toList());
}
}
Try like below :
Group:
#JsonIgnoreProperties(ignoreUnknown=true)
public class Group {
#JsonSerialize(using= TitleSerializer.class)
List<User> members;
//getters and setters
}
User:
public class User {
private String title;
//getters and setters
}
Custom Serializer :
public class TitleSerializer extends StdSerializer<List<User>> {
private static List<User> users=new ArrayList<User>();
protected TitleSerializer(Class<List<User>> t) {
super(t);
// TODO Auto-generated constructor stub
}
#SuppressWarnings("unchecked")
public TitleSerializer(){
this((Class<List<User>>) users.getClass());
}
#Override
public void serialize(List<User> users, JsonGenerator paramJsonGenerator,
SerializerProvider paramSerializerProvider) throws IOException {
paramJsonGenerator.writeStartObject();
List<String> titles=new ArrayList<String>(users.size());
for(User user: users){
titles.add(user.getTitle());
}
paramJsonGenerator.writeObjectField("members", titles);
paramJsonGenerator.writeEndObject();
}
}
Test :
Group group=new Group(Arrays.asList(new User("a"),new User("b"),new User("c")));
ObjectMapper mapper = new ObjectMapper();
String serialized = mapper.writeValueAsString(group);
System.out.println("output "+serialized);
Output:
{"members":["a","b","c"]}

Custom JSON Deserialization with Jackson

I'm using the Flickr API. When calling the flickr.test.login method, the default JSON result is:
{
"user": {
"id": "21207597#N07",
"username": {
"_content": "jamalfanaian"
}
},
"stat": "ok"
}
I'd like to parse this response into a Java object:
public class FlickrAccount {
private String id;
private String username;
// ... getter & setter ...
}
The JSON properties should be mapped like this:
"user" -> "id" ==> FlickrAccount.id
"user" -> "username" -> "_content" ==> FlickrAccount.username
Unfortunately, I'm not able to find a nice, elegant way to do this using Annotations. My approach so far is, to read the JSON String into a Map<String, Object> and get the values from there.
Map<String, Object> value = new ObjectMapper().readValue(response.getStream(),
new TypeReference<HashMap<String, Object>>() {
});
#SuppressWarnings( "unchecked" )
Map<String, Object> user = (Map<String, Object>) value.get("user");
String id = (String) user.get("id");
#SuppressWarnings( "unchecked" )
String username = (String) ((Map<String, Object>) user.get("username")).get("_content");
FlickrAccount account = new FlickrAccount();
account.setId(id);
account.setUsername(username);
But I think, this is the most non-elegant way, ever. Is there any simple way, either using Annotations or a custom Deserializer?
This would be very obvious for me, but of course it doesn't work:
public class FlickrAccount {
#JsonProperty( "user.id" ) private String id;
#JsonProperty( "user.username._content" ) private String username;
// ... getter and setter ...
}
You can write custom deserializer for this class. It could look like this:
class FlickrAccountJsonDeserializer extends JsonDeserializer<FlickrAccount> {
#Override
public FlickrAccount deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Root root = jp.readValueAs(Root.class);
FlickrAccount account = new FlickrAccount();
if (root != null && root.user != null) {
account.setId(root.user.id);
if (root.user.username != null) {
account.setUsername(root.user.username.content);
}
}
return account;
}
private static class Root {
public User user;
public String stat;
}
private static class User {
public String id;
public UserName username;
}
private static class UserName {
#JsonProperty("_content")
public String content;
}
}
After that, you have to define a deserializer for your class. You can do this as follows:
#JsonDeserialize(using = FlickrAccountJsonDeserializer.class)
class FlickrAccount {
...
}
Since I don't want to implement a custom class (Username) just to map the username, I went with a little bit more elegant, but still quite ugly approach:
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(in);
JsonNode user = node.get("user");
FlickrAccount account = new FlickrAccount();
account.setId(user.get("id").asText());
account.setUsername(user.get("username").get("_content").asText());
It's still not as elegant as I hoped, but at least I got rid of all the ugly casting.
Another advantage of this solution is, that my domain class (FlickrAccount) is not polluted with any Jackson annotations.
Based on #MichaƂ Ziober's answer, I decided to use the - in my opinion - most straight forward solution. Using a #JsonDeserialize annotation with a custom deserializer:
#JsonDeserialize( using = FlickrAccountDeserializer.class )
public class FlickrAccount {
...
}
But the deserializer does not use any internal classes, just the JsonNode as above:
class FlickrAccountDeserializer extends JsonDeserializer<FlickrAccount> {
#Override
public FlickrAccount deserialize(JsonParser jp, DeserializationContext ctxt) throws
IOException, JsonProcessingException {
FlickrAccount account = new FlickrAccount();
JsonNode node = jp.readValueAsTree();
JsonNode user = node.get("user");
account.setId(user.get("id").asText());
account.setUsername(user.get("username").get("_content").asText());
return account;
}
}
You can also use SimpleModule.
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override public JsonDeserializer<?> modifyDeserializer(
DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass() == YourClass.class) {
return new YourClassDeserializer(deserializer);
}
return deserializer;
}});
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
objectMapper.readValue(json, classType);
I made it this way:
public class FlickrAccount {
private String id;
#JsonDeserialize(converter = ContentConverter.class)
private String username;
private static class ContentConverter extends StdConverter<Map<String, String>, String> {
#Override
public String convert(Map<String, String> content) {
return content.get("_content"));
}
}
}
You have to make Username a class within FlickrAccount and give it a _content field

Categories