I want to send json, which have another json object inside, like this
{
"key1": "value1",
"key2": "valu2",
"content": {
"nestedkey1": "nestedValue1",
"nestedkey2": "nestedValue2"
}
}
Object inside doesn't have any java representation, just string in json-format. How it can be correctly converted?
My approach is not correct, I always receive empty string for nested json. I used Map for this nested object, but empty map again.
public class Instance {
private String key1;
private int key2;
private String content;
public String getKey1 {
return key1;
}
public void setKey1(String key1) {
this.key1 = key1;
}
public BigDecimal getKey2() {
return key2;
}
public void setKey2(BigDecimal key2) {
this.key2 = key2;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
How is the JSON serialization being done? If you're not using Jackson, then you should be.
Jackson can take a Map and translate it to JSON just like you want, without any additional configuration. On the other hand, if you're using the Jersey JSON plugin, you'd have to write a subclass of Map, and add JAXB annotations to it - kind of a pain in the ass.
Related
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 am using Jackson to deserialize a JSON string into an enum.
public enum RoomType {
SHARED("shared"),
PRIVATE("private");
private String value;
RoomType(String value) {
this.value = value;
}
#JsonCreator
public static RoomType fromJson(final String jsonValue) {
for (RoomType type : values()) {
if (type.value.equals(jsonValue)) {
return type;
}
}
return null;
}
#JsonValue
#Override
public String toString() {
return value;
}
}
I want to unit test the different edge cases:
#RunWith(JUnit4.class)
public class RoomTypeTest {
private final ObjectMapper mapper = new ObjectMapper();
#Test
public void fromJsonWithShared() throws Exception {
String json = "{\"roomType\":\"shared\"}";
RoomType type = mapper.readValue(json, RoomType.class);
assertThat(type).isEqualTo(RoomType.SHARED);
}
}
The test fails. When I debug I see that jsonValue is null when RoomType.fromJson is invoked. Seems like that Jackson does not pick up the value from the JSON string.
Related examples
EnumCreatorTest929.java
I think Jackson doesn't know what value to pass to that fromJson method. Try adding #JsonProperty:
#JsonCreator
public static RoomType fromJson(#JsonProperty("roomType") final String jsonValue) {
....
}
I am trying to convert the following JSON structure (part of a larger JSON object) to a POJO but getting the exception copied below (using Java/Jackson).
JSON
"outputKeys":
{"ABC":"gGyIioUr4Jfr5QiCm6Z==",
"DEF":"RxHfNyD2JyPOpG5tv3Jaj5g=="}
Java class
private class OutputKeys {
private String key;
private String value;
public OutputKeys(String key, String value) {
this.key = key;
this.value = value;
}
}
&
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(jsonString, Test.class);
exception:
no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?
Test class has the OutputKeys as an attribute.
Any suggestions would be welcome. I have tried using a List of OutputKeys as well .
Update:
I have tried the following without success:
class OutputKeys {
public Map<String, String> keys;
///with constructor/setter/getters
}
&
class OutputKeys {
public List<OutputKey> keys;
///with constructor/setter/getters
public class OutputKey {
Map<String, String> outputs = new HashMap<>();
// tried this too:
// String key
//String value
}
You require below mentioned single class only, containing
All keys(ABC and DEF)
getters/setters
toString() which you'll use interact with JSON.
public class OutputKeys
{
private String ABC;
private String DEF;
public String getABC ()
{
return ABC;
}
public void setABC (String ABC)
{
this.ABC = ABC;
}
public String getDEF ()
{
return DEF;
}
public void setDEF (String DEF)
{
this.DEF = DEF;
}
#Override
public String toString()
{
return "ClassPojo [ABC = "+ABC+", DEF = "+DEF+"]";
}
}
Let me know if you require more details.
Since the keys were dynamic, I ended up deserializing the data using the iterator on the JsonNode:
jsonNode.get("outputKeys").iterator()
& then getting the relevant dynamic key information via the iterator.
I needed a similar tool for NodeJS. So that I can write tests on parts of a bigger model that was serialized (JSON).
So, if I need only "ABC":"gGyIioUr4Jfr5QiCm6Z==" or "XYZ":{"Hello": "My String", "Content": [1,2,3]}, the only property I care to test at the moment is:
var sutXYX = { Hello: "My String", Content: [ 1, 2, 2]};
I wrote this tool as a utility https://github.com/whindes/PojoScriptifyFromJSON
I have a JSON array like as shown below which I need to serialize it to my class. I am using Jackson in my project.
[
{
"clientId": "111",
"clientName": "mask",
"clientKey": "abc1",
"clientValue": {}
},
{
"clientId": "111",
"clientName": "mask",
"clientKey": "abc2",
"clientValue": {}
}
]
In above JSON array, clientValue will have another JSON object in it. How can I serialize my above JSON array into my java class using Jackson?
public class DataRequest {
#JsonProperty("clientId")
private String clientId;
#JsonProperty("clientName")
private int clientName;
#JsonProperty("clientKey")
private String clientKey;
#JsonProperty("clientValue")
private Map<String, Object> clientValue;
//getters and setters
}
I have not used jackson before so I am not sure how can I use it to serialize my JSON array into Java objects? I am using jackson annotation here to serialize stuff but not sure what will be my next step?
You can create a utility function shown below. You may want to change the Deserialization feature based on your business needs. In my case, I did not want to fail on unknown properties => (FAIL_ON_UNKNOWN_PROPERTIES, false)
static <T> T mapJson(String body,
com.fasterxml.jackson.core.type.TypeReference<T> reference) {
T model = null;
if(body == null) {
return model;
}
com.fasterxml.jackson.databind.ObjectMapper mapper =
new com.fasterxml.jackson.databind.ObjectMapper();
mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
try {
model = mapper.readValue(body, reference);
} catch (IOException e) {
//TODO: log error and handle accordingly
}
return model;
}
You can call it using similar approach as shown below:
mapJson(clientValueJsonString,
new com.fasterxml.jackson.core.type.TypeReference<List<DataRequest>>(){});
You can try #JsonAnyGetter and #JsonAnySetter annotations with an inner class object. Also clientName should have type String, not int.
public class DataRequest {
private String clientId;
private String clientName;
private String clientKey;
private ClientValue clientValue;
//getters and setters
}
public class ClientValue {
private Map<String, String> properties;
#JsonAnySetter
public void add(String key, String value) {
properties.put(key, value);
}
#JsonAnyGetter
public Map<String,String> getProperties() {
return properties;
}
}
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.