I've a problem with deserialization of Id class. I have class:
public class Id{
private String raw;
public Id(String raw) {
this.raw = raw;
}
public abstract String getRaw() {
return raw;
}
}
and MixIn:
abstract class IdMixIn {
#JsonCreator
public IdMixIn(String raw) {
}
#JsonValue
public abstract String getRaw();
}
and json array:
[
"test-id",
"null",
null
]
First two Id-s after deserialization are correct - they are (Id(raw=null)) but the third one is simply null. Can I somehow achieve same effect for "null" and null?
Thanks for help!
Related
Im trying to convert a java object to a json according to an API's declared pattern, using jakson.
but I couldnt figure it out.
how should I do that?
Here are my classes
ProcessValueBaseDto
package com.ir.tsn;
public class ProcessValueBaseDto implements Serializable {
private String id;
private ProcessValue value;
}
//methods Ommited
ProcessValue
package com.ir.tsn;
public interface ProcessValue<T> extends Serializable {
void setValues(T values);
}
//methods Ommited
ProcessValueDto
package com.ir.tsn;
public class ProcessValueDto implements
ProcessValue<List<String>> {
private List<String> values;
}
//methods Ommited
the excpected json should be like this
{
"id": "id1",
"value": {
"com.alz.ProcessValueDto": {
"values": [
"500000000"
]
}
}
}
com.alz.ProcessValueDto is the name of one of the ProcessValue.class implementations in API
thank you in advance
I can't really understand the question, but the one problem I see from the code you posted is that you have two classes with the same name. Also if the code is all in the same file you should split it up as you can't have more than one public class or interface in the same file.
You can use the given below class for given JSON.
public class MyValue
{
public List<string> values { get; set; }
}
public class Value
{
public MyValue MyValue { get; set; }
}
public class RootObject
{
public string id { get; set; }
public Value value { get; set; }
}
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;
}
}
I'm trying to deserialize some JSON to a generic class. The structure is roughly as follows:
public abstract class AbstractRequest implements Constants
{
public abstract Class<?> getClazz();
}
public class GetTransaction extends AbstractTransactionRequest
{
#Override
public Class<Transaction> getClazz()
{
return Transaction.class;
}
}
And the Transaction class is as follows:
public class Transaction implements Serializable
{
#SerializedName("_id")
private String id;
private int amount;
#SerializedName("details")
private Map<String, String> transactionDetails;
private class Details {
private String issuer;
#SerializedName("redirect_url")
private String redirectUrl;
#SerializedName("approval_url")
private String approvalUrl;
}
}
All classes are slightly more complicated but I removed irrelevant variables.
Here's a JSON sample:
{
"_id": "2740096e-58a0-4677-8947-84fcc54cfaad",
"amount": 456,
"details": {
"issuer": "MYBANK",
"redirect_url": "https://example.com/redirect/MYBANK",
"approval_url": "https://example.com/v1/transaction/2740096e-58a0-4677-8947-84fcc54cfaad/MYBANK/authorize"
}
}
Now, I deserialize this code by doing
response.setData(Gson.fromJson(this.getResponse(), this.request.getClazz()));
Where setData accepts a Object, and getResponse returns the JSON as a String. I then do (Transaction) response.getData() which casts data to a Transaction. However, this is always null. Can anyone tell my why?
Sorry for the potentially confusing code!
I have problems deserializing Enums that have multiple names for a value. Here is an example: Info is a Java class that inside has an enum with multiple names:
public class Info {
//...
private ContainerFormat format;
}
// ContainerFormat.java:
public enum ContainerFormat {
// ....
MP4("mp4", "mpeg4"),
UNKNOWN("null");
private String name;
private List<String> others;
ContainerFormat(String name) {
this.name = name;
}
/** The service does not always return the same String for output formats.
* This 'other' string fixes the deserialization issues caused by that.
*/
ContainerFormat(String name, String... others) {
this.name = name;
this.others = new ArrayList<String>();
for (String other : others) {
this.others.add(other);
}
}
#JsonValue
#Override
public String toString() {
return name;
}
public List<String> otherNames() {
return others;
}
#JsonCreator
public static ContainerFormat fromValue(String other) throws JsonMappingException {
for (ContainerFormat format : ContainerFormat.values()) {
if (format.toString().equalsIgnoreCase(other)) {
return format;
}
if (format.otherNames() != null && format.otherNames().contains(other)) {
return format;
}
}
return UNKNOWN;
}
}
The problem is when I deserialize something that contains "mpeg4" instead of mp4 I get this error:
com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not construct instance of com.foo.ContainerFormat from String value 'mpeg4': value not one of declared Enum instance names
at [Source: N/A; line: -1, column: -1] (through reference chain: com.foo.Info["format"])
at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:55)
at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:650)
at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:85)
at com.fasterxml.jackson.databind.deser.std.EnumDeserializer.deserialize(EnumDeserializer.java:20)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:375)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:98)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:308)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:2769)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1478)
at com.fasterxml.jackson.databind.ObjectMapper.treeToValue(ObjectMapper.java:1811)
Any pointers on how to fix this?
TIA
I found a good solution based on Florin's answer:
the correct configuration with jackson 2.7.0-rc2 (and probably also before)
private ObjectMapper createObjectMapper() {
final ObjectMapper mapper = new ObjectMapper();
// enable toString method of enums to return the value to be mapped
mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
return mapper;
}
In your enum you just have to override the toString() method:
public enum EXAMPLE_TYPE {
START("start"),
MORE("more");
// the value which is used for matching
// the json node value with this enum
private final String value;
SectionType(final String type) {
value = type;
}
#Override
public String toString() {
return value;
}
}
You don't need any annotations or custom deserializers.
Get rid of String name and List<String> other and instead have just one field - List<String> names and serialize the single getter with #JsonValue
public enum ContainerFormat {
// ....
MP4("mp4", "mpeg4"),
UNKNOWN("null");
private List<String> names;
ContainerFormat(List<String> names) {
this.names = new ArrayList<String>(names);
}
#JsonValue
public List<String> getNames()
{
return this.names;
}
#JsonCreator
public static ContainerFormat getContainerFromValue(String value) throws JsonMappingException {
for (ContainerFormat format : ContainerFormat.values()) {
if(format.getValues().contains(value))
return format;
}
return UNKNOWN;
}
Alternatively, if you choose to keep your existing code, you could try annotating otherValues() with #JsonValue
Well, I found a workaround: one of these flags does the right thing and allows me to read that mpeg4 back in:
mapper.configure(org.codehaus.jackson.map.SerializationConfig.Feature.WRITE_NULL_PROPERTIES, false);
mapper.configure(org.codehaus.jackson.map.SerializationConfig.Feature.WRITE_ENUMS_USING_TO_STRING, true);
mapper.configure(org.codehaus.jackson.map.DeserializationConfig.Feature.READ_ENUMS_USING_TO_STRING, true);
mapper.setPropertyNamingStrategy(org.codehaus.jackson.map.PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
mapper.setSerializationInclusion(org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion.NON_EMPTY);
mapper.configure(org.codehaus.jackson.map.DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
I am using Retrofit to make a HTTP request which returns an array of object and I am getting the following errors:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY
The response returned is expected to be like this:
[ {key1: "value1", key2: "value2"}, {key1: "value1", key2: "value2"}, ... ]
I have the following class, for serializing the data:
public class data {
private List<element> dataList;
public List<element> getElements() {
return dataList;
}
public class element {
#SerializedName("key1")
private String key1;
#SerializedName("key2")
private String key2;
// Getters and Setters
}
}
Please let me know if you have any ideas. Thanks
The error was actually in my implementation of Retrofit Callback. My implementation was expecting an object when it should be expecting an array in this case. Thanks everyone for the help.
Before
//*****MyData*****//
public class MyData {
private List<Data> dataList;
public List<Data> getElements() {
return dataList;
}
public class Data {
#SerializedName("key1")
private String key1;
#SerializedName("key2")
private String key2;
// Getters and Setters
}
}
//*****Callback Implementation*****//
public class MyDataCallback extends Callback {
public MyDataCallback(MyDataCallbackListener<MyData> myDataCallbackListener) {
super(myDataCallbackListener);
}
#Override
public void success(MyData data, Response response) {
if (myDataCallbackListener != null) {
myDataCallbackListener.onCallbackComplete(true, response, MyDataCallback.CALLBACK_SUCCESS_MESSAGE, data);
}
}
}
After
//*****Data*****//
public class Data {
#SerializedName("key1")
private String key1;
#SerializedName("key2")
private String key2;
// Getters and Setters
}
//*****Callback Implementation*****//
public class MyDataCallback extends Callback {
public MyDataCallback(MyDataCallbackListener<List<Data>> myDataCallbackListener) {
super(myDataCallbackListener);
}
#Override
public void success(List<Data> data, Response response) {
if (myDataCallbackListener != null) {
myDataCallbackListener.onCallbackComplete(true, response, MyDataCallback.CALLBACK_SUCCESS_MESSAGE, data);
}
}
}
As Dave mentioned in his comment, it does seem strange that you have recursion in the class that I am assuming is your response object. (your class "data" has a list of "data" objects).
I would suggest something a little more strait forward such as this:
public class ResponseObject {
private ArrayList<DataObject> mDataObjects;
public ArrayList<DataObject> getDataObjects() {
return mDataObjects;
}
private class DataObject {
private String key1;
private String key2;
public String getKey1() {
return key1;
}
public String getKey2() {
return key2;
}
}
}
or since you are local maybe you can buy Jake a beer :) From his photo, I would check Rouge Ales, 21 Amendment or my favorite last time I was in SF - Magnolia
It's not valid JSON to begin with an array. You need to instead return something like this:
{
dataList: [
{
key1: "value1",
key2: "value2"
},
{
key1: "value3",
key2: "value4"
}
]
}
Then you can use GSON to deserialize that into your data class.