I am working with Java Spring framework and receive a JSON on input. It consists of an array of thousands of MyObjects. I would like the application to throw exception if one of the MyObjects or AnotherObjects fails a constraint (#NotNull or #Size).
So far, the application throws NullPointerException once it is accessing the null field when doing something with the object - not during its construction.
My question is:
Is there a way to check the constraints of nested JSON objects preferrably with annotation?
The JSON looks like:
{
"myObjects": [
{
"code": "PQ",
"another_objects": [
{
"attr1": "value1",
"attr2": "value2",
"attrN": "valueN"
},
{
"attr1": "value1",
"attr2": "value2",
"attrN": "valueN"
}
]
},
{
...
}
]
}
The servlet looks like:
#RequestMapping(value = ...)
public final void doSomething(#Valid #RequestBody MyObjectWrapper wrapper) {
// do something very time-heavy here
}
The objects are defined as follows:
public class MyObjectWrapper {
private List<MyObject> myObjects;
public List<MyObject> getMyObjects() {
return myObjects;
}
public void setMyObjects(List<MyObjects> myObjects) {
this.myObjects = myObjects;
}
}
And the MyObject class:
public class MyObject {
#NotNull
#NotEmpty(message = ...)
List<AnotherObject> anotherObjects;
#NotNull
#Size(min = 2, max = 2, message = ...)
String code;
#JsonCreator
public MyObject(#JsonProperty("another_objects") List<AnotherObjects> anotherObjects,
#JsonProperty("code") String code) {
this.code = code;
this.anotherObjects = anotherObjects;
}
/* getters and setters */
}
The AnotherObjects is similar but consists of Strings only.
Add #Valid to the nested object of the wrapper
public class MyObjectWrapper {
#Valid
private List<MyObject> myObjects;
public List<MyObject> getMyObjects() {
return myObjects;
}
public void setMyObjects(List<MyObjects> myObjects) {
this.myObjects = myObjects;
}
}
Related
I have a Person class:
public class Person {
private String name;
private String address;
}
Now this Person class is used in another class as:
public class Group {
private Person nicePerson;
private Person badPerson;
private Person secretPerson;
}
Now, I want to serialize my Group object to JSON using Jackson ObjectMapper. I'm using a custom serializer based on some annotation.
When I serialize the Group object, I'm looking for the following output:
{
"nicePerson": {
"name": "John",
"address": "usa"
},
"badPerson": {
"name": "Harold",
"address": "canada"
},
"secretPerson": {
"name": "XXXXXXX",
"address": "london"
}
}
Here, I want to change the name of 'secretPerson' to "XXXXXXX".
But I'm having some problems.
I have made following annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD})
public #interface SecretInfo {
String maskedValue = "XXXXXXX";
String value() default maskedValue;
}
I have annotated my Person class following way:
public class Person {
#SecretInfo
private String name;
private String address;
}
I have made the following serializer which looks for this annotation and then changes the value. It is using some generics now for convenience.
public class SecretInfoSerializer<T> extends StdSerializer<T> implements ContextualSerializer {
private SecretInfo secretInfo;
public SecretInfoSerializer() {
super((Class<T>) String.class);
}
public SecretInfoSerializer(SecretInfo secretInfo,
JavaType clazz) {
super(clazz);
this.secretInfo = secretInfo;
}
#Override
public void serialize(T value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (secretInfo != null && value != null) {
jsonGenerator.writeString(secretInfo.value());
} else jsonGenerator.writeString(getValue(value));
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty property) {
if (property != null) {
return new SecretInfoSerializer<>(
property.getAnnotation(SecretInfo.class),
property.getType());
} else {
return new SecretInfoSerializer<>();
}
}
private String getValue(T value) {
if (value == null) {
return "null";
} else {
return value.toString();
}
}
}
I have associated this with Annotation Introspector and made the ObjectMapper.
Then I serialized my 'Group' class above. But I get the following output:
{
"nicePerson": {
"name": "XXXXXXX",
"address": "usa"
},
"badPerson": {
"name": "XXXXXXX",
"address": "canada"
},
"secretPerson": {
"name": "XXXXXXX",
"address": "london"
}
}
As you can see, since I annotated the 'name' field in Person class, I'm getting "XXXXXXX" for every Person class there is. But this is not what I wanted. I just want to change the value for 'secretPerson' only.
I don't know what to do:
Maybe there is some way such as following:
public class Group {
private Person nicePerson;
private Person badPerson;
#HasSecretInfo
private Person secretPerson;
}
Then the serializer would check if it #HasSecretInfo, only then look for #SecretInfo ...etc. something like this.
So I'm looking to solve this problem. If you have any solution, that'd be great.
I am looking to serialize my class using gson but I would like to omit the hashmap name. Is this possible with gson?
I have tried writing my own TypeAdapter but the map name is still written as the parent object.
I have a class which looks like
public class myClass {
#Expose
public Long timestamp;
#Expose
public String id;
#Expose
public HashMap<String, someOtherClass> myMap = new HashMap<>();
#Override
public String toString() {
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
return gson.toJson(this);
}
}
current output :
{
"timestamp": 1517245340000,
"id": "01",
"myMap": {
"mapKey1": {
"otherClassId": "100", // works as expected
}
"mapKey2": {
"otherClassId": "101", // works as expected
}
}
}
What I am hoping to get :
{
"timestamp": 1517245340000,
"id": "01",
"mapKey1": {
"otherClassId": "100", // works as expected
},
"mapKey2": {
"otherClassId": "100", // works as expected
}
}
Write your own TypeAdapter. See javadoc for example.
Specify it with #JsonAdapter annotation, or register it with GsonBuilder.
#JsonAdapter(MyClassAdapter.class)
public class MyClass {
public Long timestamp;
public String id;
public HashMap<String, SomeOtherClass> myMap = new HashMap<>();
}
public class MyClassAdapter extends TypeAdapter<MyClass> {
#Override public void write(JsonWriter out, MyClass myClass) throws IOException {
// implement the write method
}
#Override public MyClass read(JsonReader in) throws IOException {
// implement the read method
return ...;
}
}
I want to use Jackson to deserialise my JSON, from Jira, into a set of POJOs. I have most of what I want working beautifully, now I just have to decode the custom field values.
My input JSON looks like:
{
"expand": "renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations",
"id": "104144",
"self": "https://jira.internal.net/rest/api/2/issue/104144",
"key": "PRJ-524",
"fields": {
"summary": "Redo unit tests to load from existing project",
"components": [],
"customfield_10240": {
"self": "https://jira.internal.net/rest/api/2/customFieldOption/10158",
"value": "Normal",
"id": "10158"
}
}
I can trivially load the summary and components, since I know ahead of time what the name of those JSON elements are, and can define them in my POJO:
#JsonIgnoreProperties({ "expand", "self", "id", })
public class JiraJson
{
private JiraFields fields;
private String key;
public JiraFields getFields()
{
return fields;
}
public String getKey()
{
return key;
}
public void setFields(JiraFields newFields)
{
fields = newFields;
}
public void setKey(String newKey)
{
key = newKey;
}
}
And similarly for JiraFields:
#JsonIgnoreProperties({ "issuetype", "priority", "status" })
public class JiraFields
{
private List<JiraComponent> components;
private String summary;
public List<JiraComponent> getComponents()
{
return components;
}
public String getSummary()
{
return summary;
}
public void setComponents(List<JiraComponent> newComponents)
{
components = newComponents;
}
public void setSummary(String newSummary)
{
summary = newSummary;
}
}
However, the field custom_10240 actually differs depending on which Jira system this is run against, on one it is custom_10240, on another it is custom_10345, so I cannot hard-code this into the POJO. Using another call, it is possible to know at runtime, before the deserialisation starts, what the name of the field is, but this is not possible at compile time.
Assuming that I want to map the value field into a String on JiraFields called Importance, how do I go about doing that? Or perhaps simpler, how to map this Importance onto a JiraCustomField class?
You can use a method annotated with #JsonAnySetter that accepts all properties that are undefined (and not ignored). in case of a Json Object (like the custom field in the question) Jackson passes a Map that contains all the Object properties (it may even contain Map values in case of nested objects). You can now at run time extract whatever properties you want:
#JsonIgnoreProperties({ "issuetype", "priority", "status" })
public class JiraFields
{
private List<JiraComponent> components;
private String summary;
private String importance;
// getter/setter omitted for brevity
#JsonAnySetter
public void setCustomField(String name, Object value) {
System.out.println(name); // will print "customfield_10240"
if (value instanceof Map) { // just to make sure we got a Json Object
Map<String, Object> customfieldMap = (Map<String, Object>)value;
if (customfieldMap.containsKey("value")) { // check if object contains "value" property
setImportance(customfieldMap.get("value").toString());
}
}
}
}
After searching further, I finally found the JsonAlias annotation. This is still defined at compile time, but I had something that I could search further on!
Further searching, and I found PropertyNamingStrategy, which allows you to rename what JSON field name is expected for a setter/field. This has the advantage in that this is done via a method, and the class can be constructed at runtime.
Here is the class that I used to perform this mapping:
import java.util.Map;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
public final class CustomFieldNamingStrategy
extends PropertyNamingStrategy
{
private static final long serialVersionUID = 8263960285216239177L;
private final Map<String, String> fieldRemapping;
private final Map<String, String> reverseRemapping;
public CustomFieldNamingStrategy(Map<String, String> newFieldRemappings)
{
fieldRemapping = newFieldRemappings;
reverseRemapping = fieldRemapping.entrySet()//
.stream()//
.collect(Collectors.toMap(Map.Entry::getValue,
Map.Entry::getKey));
}
#Override
public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName)
{
if (field.getDeclaringClass().getName().equals(JiraFields.class.getName()))
{
return reverseRemapping.getOrDefault(defaultName, defaultName);
}
return defaultName;
}
#Override
public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method,
String defaultName)
{
if (method.getDeclaringClass().getName().equals(JiraFields.class.getName()))
{
return reverseRemapping.getOrDefault(defaultName, defaultName);
}
return defaultName;
}
#Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method,
String defaultName)
{
if (method.getDeclaringClass().getName().equals(JiraFields.class.getName()))
{
return reverseRemapping.getOrDefault(defaultName, defaultName);
}
return defaultName;
}
}
My Json looks something like (and its unmodifiable)
{
....
"Sale": [
{ "SaleLines": {
"SaleLine": [
{
"unitPrice": "190",
"unitQuantity": "1"
}
],
"calcDiscount": "0",
"calcSubtotal": "500"
}
} ]
}
The java POJO code looks like
public static class SaleLines {
#JsonProperty("SaleLine")
private ArrayList<SaleLine> saleLine;
public ArrayList<SaleLine> getSaleLine() { return saleLine; }
public void setSaleLine(ArrayList<SaleLine> saleLine) { this.saleLine = saleLine; }
}
public static class SaleLine {
#JsonProperty("itemID")
private String itemId; //line_item_nk
#JsonProperty("unitPrice")
private String unitPrice;
....
}
#JsonPropertyOrder({"total", "calcSubTotal", "calcDiscount"})
public static class Sale {
private String saleTotal, calcSubtotal, calcDiscount;
private int salesValueWOVat;
#JsonProperty("SaleLines")
SaleLines saleLine;
#JsonCreator
public Sale (#JsonProperty("total")String saleTotal,
#JsonProperty("calcSubtotal")String calcSubtotal,
#JsonProperty("calcDiscount")String calcDiscount,
#JsonProperty("SaleLines")SaleLines saleLine,
) {
this.saleTotal = saleTotal;
this.calcSubtotal = calcSubtotal;
this.calcDiscount = calcDiscount;
this.saleLine = saleLine;
setSalesValueWOVat();
}
// getter and setters
}
#SuppressWarnings({ "rawtypes" })
public static <E, T extends Collection> T readFromJsonAndFillType (
String json,
Modules module,
Class <T> collectionType,
Class <E> elementType)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
TypeFactory tf = objMapper.getTypeFactory();
JsonNode node = objMapper.readTree(json).get(module.jsonFieldName);
return objMapper.readValue(node.toString(),
tf.constructCollectionType(collectionType, elementType));
}
In main
ArrayList<Sale> saleList = readFromJsonAndFillType(
saleJSON,
Modules.SALE,
ArrayList.class,
Sale.class);
for (Sale sale: saleList) {
System.out.println(sale.getSaleLines().getSaleLine().size()); //ERROR Null Pointer Exception
System.out.println(sale.toString());
}
So, the problem is that the SaleLine does not get populated as expected
It is possible that your JSON is invalid; e.g. there are commas missing in the latest version in your Question.
If the problem is that your JSON is syntactically invalid, then you will either need to hack the JSON before you parse it or hack the parser to accept invalid JSON.
On the other hand, it is possible that some of your JSON records are missing the SaleLine or SaleLines attributes or have a null instead of one of the values. If that is possible, add some null tests ... and reject the record or cope with the missing data.
My Json looks something like (and its unmodifiable)
{
....
"Sale": [
"SaleLines": {
"SaleLine": [
{
"Item": {
"Prices": {
"ItemPrice": [
{
"amount": "100",
"useType": "Default"
},
{
"amount": "100",
"useType": "MSRP"
}
]
},
}
......
......
}
]
"calcDiscount": "0",
"calcSubtotal": "500",
}
]
}
The java POJO code looks like
public static class SaleLines {
#JsonProperty("SaleLine")
private SaleLineObject[] saleLineObject;
public SaleLineObject[] getSaleLineObject() { return saleLineObject; }
public void setSaleLineObject(SaleLineObject[] saleLineObject) { this.saleLineObject = saleLineObject; }
}
public static class SaleLineObject {
private SaleLine saleLine;
public SaleLine getSaleLine() {
return saleLine;
}
public void setSaleLine(SaleLine saleLine) {
this.saleLine = saleLine;
}
}
public static class SaleLine {
#JsonProperty("itemID")
private String itemId; //line_item_nk
#JsonProperty("unitPrice")
private String unitPrice;
....
}
#JsonPropertyOrder({"total", "calcSubTotal", "calcDiscount"})
public static class Sale {
private String saleTotal, calcSubtotal, calcDiscount;
private int salesValueWOVat;
#JsonProperty("SaleLines")
SaleLines saleLine;
#JsonCreator
public Sale (#JsonProperty("total")String saleTotal,
#JsonProperty("calcSubtotal")String calcSubtotal,
#JsonProperty("calcDiscount")String calcDiscount,
#JsonProperty("SaleLines")SaleLines saleLine,
) {
this.saleTotal = saleTotal;
this.calcSubtotal = calcSubtotal;
this.calcDiscount = calcDiscount;
this.saleLine = saleLine;
setSalesValueWOVat();
}
// getter and setters
}
#SuppressWarnings({ "rawtypes" })
public static <E, T extends Collection> T readFromJsonAndFillType (
String json,
Modules module,
Class <T> collectionType,
Class <E> elementType)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
TypeFactory tf = objMapper.getTypeFactory();
JsonNode node = objMapper.readTree(json).get(module.jsonFieldName);
return objMapper.readValue(node.toString(),
tf.constructCollectionType(collectionType, elementType));
}
In main
ArrayList<Sale> saleList = readFromJsonAndFillType(
saleJSON,
Modules.SALE,
ArrayList.class,
Sale.class);
for (Sale sale: saleList) {
System.out.println(sale.toString());
}
I know this question has been asked multiple times and even I took help from for eg
Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
But still I cannot get through this error
I know this question has been asked multiple times & everyone getting resolved there problems with different ways. Whenever you find "Can not deserialized instance of out of START_OBJECT token". it's generally occur when you trying to get object which is not actually same in json format (means json starting object is different not as you guys are converting).
For Ex:- Json returning first object is Boolean but unfortunately you are converting is to List<Object> then you will having this error.
I would suggest to have a look to read format using below code than convert it as per the object returning.
ObjectMapper objectMapper = new ObjectMapper();
Map<?,?> empMap = objectMapper.readValue(new FileInputStream("employee.json"),Map.class);
for (Map.Entry<?,?> entry : empMap.entrySet())
{
System.out.println("\n----------------------------\n"+entry.getKey() + "=" + entry.getValue()+"\n");
}
Get the key & convert the value as per the object returning.
For reference:- https://dzone.com/articles/processing-json-with-jackson