We have RestWS where need to pass request in JSON format. This request contains different type of values such as String, List, enum etc.
We figured out how need to pass String and List (see below) but not sure how to pass enum in JSON request object.
Sample JSON Request for List and String in request:
{"firstparam":["195","196"],"secondparam":"test"}
First param is List and second param is String. Similarly we need to know how we can pass enum (also in the above request).
Sample enum class:
#XmlType(name = "Type")
#XmlEnum
public enum Type {
#XmlEnumValue("New")
NEW("New"),
#XmlEnumValue("Delete")
DELETE("Delete"),
#XmlEnumValue("Process")
PROCESS("Process");
private final String value;
WorkingStatusType(String v) {
value = v;
}
public String value() {
return value;
}
public static WorkingStatusType fromValue(String v) {
for (WorkingStatusType c: WorkingStatusType.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
This Google JSON style guide might help you.
Related
We are parsing okhttp response objects using GSON.
In order to allow a generic parsing logic, We had opened to pass a type of response using setType(Type type) and now using below code to parse all responses.
Object mResponse = mGson.fromJson(response.body().string(), mType);
As this code is not open for a change I have extended this class and overridden the parse response method. I have also created a new class called MyRespose and want to initialize with whatever type is passed in setType method so that the consumers are returned the object of what type they have passed instead of an instance of Object class.
public class MyResponse<T> {
private int mStatus;
private T mResponseData;
public MyResponse(int status, T responseBody) {
this.mStatus = status;
mResponseData = responseBody;
}
public MyResponse(T responseBody) {
mResponseData = responseBody;
}
public int getStatus() {
return mStatus;
}
public void setStatus(int status) {
this.mStatus = status;
}
public T getResponseData() {
return mResponseData;
}
public void setResponseData(T responseData) {
mResponseData = responseData;
}
}
How can I use the Type(interface) to pass as so that the when someone calls getResponseData it return the object of type which is passed in setType instead of a generic Object ?
Something like
Object mResponse = mGson.fromJson(response.body().string(), mType);
mMyResponse = new MyResponse<???>(response.code(), mResponse );
How can i use mType to pass at place of "???"
I think, you can make use of TypeToken here. Its included in Gson and should go like this.
Type responseType = new TypeToken<MyResponse<XYZ>>() {}.getType();
gson.fromJson(json, responseType);
You can read further on this here
I'm a new member in Restful API, I'm writing a GET method:
#RequestMapping(method = RequestMethod.GET, value = "/resourcerecords", produces={"application/json", "application/xml"})
public #ResponseBody Object getRRs(#RequestBody RRRequest requestBody){
// do something
}
The RRRequest class:
public class RRRequest{
private RRREC reqObject;
// getter and setter
}
The RRREC class:
public class RRREC{
protected String infraAddr;
protected RRINFRATYPE infraType;
// getter and setter
}
And the RRINFRATYPE class:
public enum RRINFRATYPE {
V_6_ADDRESS("V6ADDRESS"),
OBJECT("OBJECT"),
ZONE("ZONE"),
V_4_REVERSEZONE("V4REVERSEZONE"),
V_6_REVERSEZONE("V6REVERSEZONE"),
NODE("NODE"),
ALL("ALL");
private final String value;
RRINFRATYPE(String v) {
value = v;
}
public String value() {
return value;
}
public static RRINFRATYPE fromValue(String v) {
for (RRINFRATYPE c: RRINFRATYPE.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
}
Then, I sent a request GET with RequestBody ( I use Fiddler Web Debugger)
"reqObject" : {
"infraAddr" : "192.168.88.4",
"infraType": {
"value": "OBJECT"
}
}
I get 400 Bad Request. If I change to
"reqObject" : {
"infraAddr" : "192.168.88.4",
"InfraType": {
"value": "OBJECT"
}
}
I can debug.
However, The reqObject only receive infraAddr with "192.168.88.4", the InfraType is null.
Who can explain to me, why I must be use "InfraType" instead of "infraType" and how to send value for InfraType.
The first one is when your api in GET method you still cant send body of request to server, try to change it to POST.
Because you use ENUM in your Object so you should define a converter like Converting JSON to Enum type with #RequestBody
But in this case, I think the fastest way is change infraType to String and use switch case with String on server side.
public class RRREC{
protected String infraAddr;
protected String infraType;
// getter and setter
}
Your JSON will be:
{
"reqObject" : {
"infraAddr" : "192.168.88.4",
"infraType": "OBJECT"
}
}
I have class that looks like this:
public class Data<U> {
#JsonProperty("difficulties")
private U[] data;
// ... geter setter constructor
}
And I don't want to create 10 more similar classes just because I need to change only one line of code (#JsonProperty("difficulties") in this case). The property value depends on Type. Is it possible to write it in one class?
Based on response of Jackson - Modify an attribute at runtime without annotation by Michał Ziober here I was able to change default field name values
by overriding PropertyNamingStrategy:
These are my received JSON examples (simplified):
{"status": "OK","error": null,"data": {
"difficulties": [{"value":"easy"},{"value":"medium"}]
}}
{"status": "ok", "error": null, "data": {
"countries": [{"code": "AT"},{"code": "BE"}]
}}
see the difference in second line where data object contains either difficulties
or countries (or many other names based on context).
Response class based on JSON response:
public class Response<T>{
private String status;
private String error;
private Data<T> data;
// Getters Setters Constructors
}
Data class based on JSON response:
public class Data<T> {
// property name, that will be changed
#JsonProperty(DataNamingStrategy.DATA_FIELD)
private T[] data;
// Getters Setters Constructors
}
And this is Naming strategy, that changes default value to runtime specified value
public class DataNamingStrategy extends PropertyNamingStrategy{
// used by other classes (this will be default field name that should be changed)
public static final String DATA_FIELD = "variable:data";
private String fieldName;
public DataNamingStrategy(String fieldName) {
this.fieldName = fieldName;
}
// use this to change field name (format "variable":"value") not needed in my case
#Override
public String nameForField(MapperConfig<?> config, AnnotatedField field,
String defaultName) {
return (defaultName.equals(DATA_FIELD))?
fieldName :
super.nameForField(config, field, defaultName);
}
// use this to change setter method field name (JSON -> Object with format "variable":{})
#Override
public String nameForSetterMethod(MapperConfig<?> config,
AnnotatedMethod method, String defaultName) {
return (defaultName.equals(DATA_FIELD))?
fieldName :
super.nameForGetterMethod(config, method, defaultName);
}
// use this to change getter method field name (Object -> JSON with format "variable":{})
// should be same as nameForSetterMethod
#Override
public String nameForGetterMethod(MapperConfig<?> config,
AnnotatedMethod method, String defaultName) {
return nameForSetterMethod(config, method, defaultName);
}
}
And usage should look like this:
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new DataNamingStrategy(tableName));
JavaType type = mapper.getTypeFactory().constructParametricType(Response.class, dataClass);
Response<U> u = mapper.readValue(result, type);
Where result is Json as String, tableName is String that will be used in JSON instead of default value and dataClass is class for U (for example Difficulty.class).
Better usage of PropertyNamingStrategy should be Map instead of one String. But I just needed to change one particular value.
Also have a look at PropertyNamingStrategy documentation or again at Michał Ziober's answer
You can use #JsonAnyGetter annotation.
public class Data<U> {
#JsonIgnore
private U[] data;
#JsonIgnore
private String propertyName;
public Data(String propertyName) {
this.propertyName = propertyName;
}
// ... geter setter
#JsonAnyGetter
public Map<String, Object> any() {
return Collections.singletonMap(propertyName, data);
}
}
And use it like below:
Data<Difficulties> difficulties = new Data<>("difficulties");
write whatever you want instead of "difficulties" string. Set your list to Data generic class instead of Difficulties object if you want
i am trying to map String to enum Object using Jackson ObjectMapper.readValue(String,Class) API, problem is Lets SAY my json string contains a Task Object with Action enum as below
public enum Action {
#XmlEnumValue("Add")
ADD("Add"),
#XmlEnumValue("Amend")
AMEND("Amend"),
#XmlEnumValue("Delete")
DELETE("Delete"),
#XmlEnumValue("Pending")
PENDING("Pending");
private final String value;
Action(String v) {
value = v;
}
public String value() {
return value;
}
public static Action fromValue(String v) {
for (Action c: Action.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
}
and the jason string will be like this "{"action":"Add"}" then ObjectMapper.readValue(jsonString, Task.Class) throws
org.codehaus.jackson.map.deser.StdDeserializationContext.weirdStringException(StdDeserializationContext.java:243) for Action Add because it cant convert this Enum.
I tried adding custom Desiserializer, But EnumDeserializer getting called anyway. any ideas?
All objects are JAXB generated, so annotations not possible.
Thanks for the help
Have you tried:
new ObjectMapper().setAnnotationIntrospector(new JaxbAnnotationIntrospector()).readValue()
we are creating a JSON REST client application that has to communicate with a service written in C#.
Most things like difference in dates etc are solved pretty easily with the FlexJson library.
But one thing doesn't: Enum values that are sent as an integer, which is the value received from the service, and that have to be mapped to their Java Enum value.
The Java enum is ok, we can convert integers to the enum as long as the value exists of course.
But we do not succeed to get the Flexjson to convert the value.
One of the Enums is called FormState
We wrote a custom Transformer let's call it OurEnumTransformer which extends AbstractTransformer and implements ObjectFactory.
Upon deserialization we add the .use(Enum.class, OurEnumTransformer), if we don't we get an error like:
Don't know how to convert 4 to enumerated constant of FormState
which makes sense as it is an integer and not a name of an enum value
But we add the .use(...) we keep getting an error on deserialization:
FormState lacks a no argument constructor. Flexjson will instantiate any protected, private, or public no-arg constructor.
But it does actually have a private parameterless constructor.
Another thing is that a breakpoint that is set in the OurEnumTransformer is never hit.
So can anyone help me why .use(Enum.class, OurEnumTransformer) does not work with an enum that has integer values?
The code of the enum and OurEnumTransformeris below
public enum FormState {
None(0),
EditMode(1),
SignedBySender(2),
AddedToRide(4),
SignedByTransporter(8),
SignedByReceiver(16),
DLT_UNKNOWN();
private int value;
private FormState() {
this.value= -1;
}
private FormState(int value) {
this.value= value;
}
public int getValue()
{
return value;
}
private static final Map<Integer, FormState> intToTypeMap = new HashMap<Integer, FormState>();
static
{
for(FormState type: FormState.values())
{
intToTypeMap.put(type.value, type);
}
}
public static FormState fromInt(int i) {
FormState type = intToTypeMap.get(Integer.valueOf(i));
if (type == null)
return FormState.DLT_UNKNOWN;
return type;
}
}
and the factory
public final class OurEnumTransformer extends AbstractTransformer implements
ObjectFactory {
#SuppressWarnings("rawtypes")
#Override
public Object instantiate(ObjectBinder context, Object value, Type targetType,
Class targetClass)
{
if(targetClass.equals(FormState.class))
return FormState.fromInt((Integer)value);
else if(targetClass.equals(TrajectState.class))
return TrajectState.fromInt((Integer)value);
else
throw new JSONException(String.format("%s: Don't know how to convert %s to enumerated constant of %s",
context.getCurrentPath(), value, targetType));
}
#Override
public void transform(Object arg0) {
// TODO Auto-generated method stub
}
}
finally the calling code:
List<JsonTrajectModel> result = null;
JSONDeserializer<List<JsonTrajectModel>> deser = new JSONDeserializer<List<JsonTrajectModel>>();
result = deser
.use(Date.class, dateTransformer)
.use("values", JsonTrajectModel.class)
.use(Enum.class, enumTransformer)
.deserialize(jsonData);
In the last code block jsonData is a valid JSON string and enumTransformer is the OurEnumTransformer instance.
A final remark, the dateTransformer used in this code does do its work.
Any help is appreciated.