How to combine these 2 jackson serializations to a single one? - java

Here's my Pojo:
public static class MyPojo {
private int rootId;
private String command;
private Double value;
// I want this property to be shown at root level
#JsonIgnore
public int getRootId() {
return rootId;
}
public void setRootId(int rootId) {
this.rootId = rootId;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
}
Here's my first serialization method:
public static void writeJsonId6() throws JsonProcessingException {
MyPojo pojo = new MyPojo();
pojo.setRootId(6);
pojo.setCommand("property.batch");
pojo.setValue(129.00);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
String json = mapper.writer().withRootName(Integer.toString(pojo.getRootId())).writeValueAsString(pojo);
System.out.println(json);
}
Json Output: {"6":{"command":"property.batch","value":129.0}}
Here's my second serialization method:
public static void writeJsonId7() throws JsonProcessingException {
MyPojo pojo = new MyPojo();
pojo.setRootId(7);
pojo.setCommand("property.batch");
pojo.setValue(88.00);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
String json = mapper.writer().withRootName(Integer.toString(pojo.getRootId())).writeValueAsString(pojo);
System.out.println(json);
}
Json Output: {"7":{"command":"property.batch","value":88.0}}
This is what I need:
{"6":{"command":"property.batch","value":129.0}, "7":{"command":"property.batch","value":88.0}}
Jackson lib versions: jackson-core:2.0.0, jackson-databind:2.9.0

Just create a Map<String, MyPojo>, containing "6" and "7" as keys, and the respective POJOs as values, and serialize that map.

You can try using Json Streams:
JsonFactory factory = new JsonFactory();
JsonGenerator generator = factory.createGenerator(new PrintWriter(System.out));
generator.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
generator.writeStartObject();
generator.writeObject(firstPojo);
generator.writeObject(secondPojo);
generator.writeEndObject();

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 remove a field from POJO

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.

Jackson json property (string) to instance

Assuming I have the following JSON:
{
"property": "123:1234"
}
How do I use Jackson annotations to ensure that the string value of "property" is de-serialized to a self-defined class rather than a String object?
I went through their documentation and I was unable to find this particular feature.
Thanks in advance.
You could create custom deserializer for your field. Assuming you want to map it to SomeClass object :
public class SomeClass {
#JsonDeserialize(using = CustomPropertyDeserializer.class)
private Properties property;
public Properties getProperty() {
return property;
}
public void setProperty(Properties property) {
this.property = property;
}
}
You annotate your field that you want to deserialize customly with #JsonDeserialize annotation passing custom deserializer.
Your deserializer could look like this :
public class CustomPropertyDeserializer extends StdDeserializer<Properties> {
public CustomPropertyDeserializer() {
super(Properties.class);
}
#Override
public Properties deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String valueAsString = p.getValueAsString();
String[] split = valueAsString.split(":");
return new Properties(split[0], split[1]);
}
}
And custom property class :
public class Properties {
private String first;
private String second;
public Properties(String first, String second) {
this.first = first;
this.second = second;
}
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getSecond() {
return second;
}
public void setSecond(String second) {
this.second = second;
}
}
For testing it :
public static void main(String[] args) throws IOException {
String s = Files.lines(Paths.get("src/main/resources/data.json")).collect(Collectors.joining());
ObjectMapper objectMapper = new ObjectMapper();
SomeClass someClass = objectMapper.readValue(s, SomeClass.class);
System.out.println(someClass.getProperty().getFirst());
System.out.println(someClass.getProperty().getSecond());
}
The output is then :
123
1234
So all the custom logic how to map your String to some class that you define could be placed in deserialize method of your custom deserializer.
First thing first define your class that needs to be used:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class JsonTest{
#JsonProperty("property")
private String property;
//define your getters and setters for the field
Then you can use the ObjectMapper class from jackson:
public static <T> T extractObjectFromJson(String jsonText, Class<T> type) {
try {
return new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).reader().forType(type)
.readValue(jsonText);
} catch (Exception e) {
//Manage your exception here
}
return null;
}
So you can just call the method extractobjectFromJson(//Your JSON String, JsonTest.class) to get your JSON deserialized.

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 cannot deserialize enum as object even if I add customized deserializer

I want to use Jackson JSON to serialize/deserialize a class containing an enum object. My class is:
class Bar {
#JsonProperty("rateType")
#JsonDeserialize(using = ReturnedRateTypeDeserializer.class)
private ReturnedRateType rateType;
public ReturnedRateType getRateType() {
return rateType;
}
public void setRateType(ReturnedRateType rateType) {
this.rateType = rateType;
}
}
The enum class ReturnedRateType is defined as:
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ReturnedRateType {
AA("AA"),
BB("BB"),
CC("CC");
#JsonProperty("value")
private String value;
ReturnedRateType(String value) {
this.value = value;
}
#JsonCreator
public static ReturnedRateType fromValue(final String value) {
if (value != null) {
for (ReturnedRateType type : ReturnedRateType.values()) {
if (value.equalsIgnoreCase(type.value)) {
return type;
}
}
}
return null;
}
}
As you see, I added #JsonFormat annotation to tell Jackson to serialize this enum as POJO, and added #JsonCreator annotation to get a static factory method from given string to enum object. Since Jackson can only serialize but can't deserialize from object representation to enum, I added the following customized deserializer for the enum ReturnedRateType:
public class ReturnedRateTypeDeserializer extends JsonDeserializer<ReturnedRateType> {
#Override
public ReturnedRateType deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ReturnedRateType type = ReturnedRateType.fromValue(jp.getValueAsString());
if(type != null)
return type;
throw new JsonMappingException("invalid value for ReturnedRateType");
}
}
But when I tested deserialization from a JSON string to enum, I got the error. The JSON string is:
{"rateType": {"value": "AA"}}
My test code is:
#Test
public void RateTypeToEnum() {
String json = "{\"rateType\": {\"value\": \"AA\"}}";
System.out.println(json);
ObjectMapper mapper = new ObjectMapper();
Bar bar = null;
try {
bar = mapper.readValue(json, Bar.class);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(bar.getRateType());
}
I expect to see the output should be AA. But jp.getValueAsString() in my customized deserializer ReturnedRateTypeDeserializer is null during the execution:
ReturnedRateType type = ReturnedRateType.fromValue(jp.getValueAsString()); //jp.getValueAsString() is null here!
Thus it returns error. So what is wrong here?
According to the Jackson 2.5.X documentation on the JsonFormat annotation the Shape.Object does not work for the enum deserialisation:
Enums: Shapes JsonFormat.Shape.STRING and JsonFormat.Shape.NUMBER can
be used to change between numeric (index) and textual (name or
toString()); but it is also possible to use JsonFormat.Shape.OBJECT
to serialize (but not deserialize).
I'd make the JsonCreator static method accept a JsonNode and read the string value from it.
Note that this would work since 2.5.X. In early versions you would need to write a custom deserialiser. Here is an example:
public class JacksonEnumObjectShape {
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
#JsonDeserialize(using = ReturnedRateTypeDeserializer.class)
public enum ReturnedRateType {
AA("AA"),
BB("BB"),
CC("CC");
#JsonProperty("value")
private String value;
ReturnedRateType(String value) {
this.value = value;
}
#JsonCreator
public static ReturnedRateType fromValue(final JsonNode jsonNode) {
for (ReturnedRateType type : ReturnedRateType.values()) {
if (type.value.equals(jsonNode.get("value").asText())) {
return type;
}
}
return null;
}
}
// can be avoided since 2.5
public static class ReturnedRateTypeDeserializer extends JsonDeserializer<ReturnedRateType> {
#Override
public ReturnedRateType deserialize(
final JsonParser jp,
final DeserializationContext ctxt) throws IOException {
final JsonNode jsonNode = jp.readValueAsTree();
return ReturnedRateType.fromValue(jsonNode);
}
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
final String json = mapper.writeValueAsString(ReturnedRateType.AA);
System.out.println(json);
System.out.println(mapper.readValue(json, ReturnedRateType.class));
}
}
Output:
{"value":"AA"}
AA

Categories