I have a json format which I am converting into Java Object Model using Jackson API. I am using Jaxsonxml 2.1.5 parser. The json response is as shown below.
{
"response": {
"name": "states",
"total-records": "1",
"content": {
"data": {
"name": "OK",
"details": {
"id": "1234",
"name": "Oklahoma"
}
}
}
}
}
Now json response format has changed. If the total-records is 1 the details will be an object with id and name attributes. But if the total-records is more than 1 then the details will be an array of object like below:
{
"response": {
"name": "states",
"total-records": "4",
"content": {
"data": {
"name": "OK",
"details": [
{
"id": "1234",
"name": "Oklahoma"
},
{
"id": "1235",
"name": "Utah"
},
{
"id": "1236",
"name": "Texas"
},
{
"id": "1237",
"name": "Arizona"
}
]
}
}
}
}
My Java Mapper class looks like below with earlier json response.
#JsonIgnoreProperties(ignoreUnknown = true)
public class MapModelResponseList {
#JsonProperty("name")
private String name;
#JsonProperty("total-records")
private String records;
#JsonProperty(content")
private Model model;
public Model getModelResponse() {
return model;
}
public void setModel(Model model) {
this.model = model;
}
}
Client Code
package com.test.deserializer;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com..schema.model.Person;
public class TestClient {
public static void main(String[] args) {
String response1="{\"id\":1234,\"name\":\"Pradeep\"}";
TestClient client = new TestClient();
try {
Person response = client.readJSONResponse(response1, Person.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public <T extends Object> T readJSONResponse(String response, Class<T> type) {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
T result = null;
try {
result = mapper.readValue(response, type);
} catch (Exception e) {
e.printStackTrace();
}
return (T) result;
}
}
Now based on the total-records how to handle to mapping to either a Model or list of Model Object. Please let me know.
You need a custom deserializer. The idea is to mix and match object processing with tree processing. Parse objects where possible but use the tree (JSONNode) for custom handling.
On the MapModelResponseList, remove the records property and add a List<Data> array where Data is just a holder class for the id/name pairs. You can get the total records by returning the size of this list.
In the deserializer, do the following:
public final class MapModelDeserializer extends BeanDeserializer {
public MapModelDeserializer(BeanDeserializerBase src) {
super(src);
}
protected void handleUnknownProperty(JsonParser jp, DeserializationContext ctxt, Object beanOrClass, String propName) throws IOException, JsonProcessingException {
if ("content".equals(propName)) {
MapModelResponseList response = (MapModelResponseList) beanOrClass;
// this probably needs null checks!
JsonNode details = (JsonNode) jp.getCodec().readTree(jp).get("data").get("details");
// read as array and create a Data object for each element
if (details.isArray()) {
List<Data> data = new java.util.ArrayList<Data>(details.size());
for (int i = 0; i < details.size(); i++) {
Data d = jp.getCodec().treeToValue(details.get(i), Data.class);
data.add(d);
}
response.setData(data);
}
// read a single object
else {
Data d = jp.getCodec().treeToValue(details, Data.class);
response.setData(java.util.Collections.singletonList(d));
}
super.handleUnknownProperty(jp, ctxt, beanOrClass, propName);
}
Note that you do not implement deserialize() - the default implementation is used to create the MapModelResponseList as normal. handleUknownProperty() is used to deal with the content element. Other data you don't care about is ignored due to #JsonIgnoreProperties(ignoreUnknown = true) in the super call.
This is a late answer, but I solve it in a different way. It can work by catching it in Object like this:
#JsonProperty("details")
public void setDetails(Object details) {
if (details instanceof List) {
setDetails((List) details);
} else if (details instanceof Map) {
setDetails((Map) details);
}
}
public void setDetails(List details) {
// your list handler here
}
public void setDetails(Map details) {
// your map handler here
}
Related
So I consume data from a REST endpoint (which I don't have access to) that is badly formatted.
In particular, I receive a json object that actually is a list. What is the best way to deal with this? Can it be done with Jackson?
{
"list": {
"element 31012991428": {
"objId": 31012991428,
"color": "green"
},
"element 31012991444": {
"objId": 31012991444,
"color": "orange"
},
"element 3101298983": {
"objId": 3101298983,
"color": "red"
},
}
}
Ideally, I want to be able to parse it as follows:
Response.java
public class GetSucherResponse {
#JsonProperty("elements") //what goes here?
private List<Element> elements;
}
Element.java
public class Element {
#JsonProperty("objId")
private Long objId;
#JsonProperty("color")
private String color;
}
Created a rough solution
Response class should look like:
#JsonDeserialize(using = ResponseDeserializer.class)
public class Response {
private List<Element> elements;
public Response(List<Element> elements) {
this.elements = elements;
}
}
Deserializer:
public class ResponseDeserializer extends StdDeserializer<Response> {
protected ResponseDeserializer() {
super(Response.class);
}
#Override
public Response deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
TreeNode rootNode = jsonParser.getCodec().readTree(jsonParser);
TreeNode listNode = rootNode.get("list");
List<Element> elements = new ArrayList<>(listNode.size());
listNode.fieldNames().forEachRemaining(
s -> elements.add(parseElement(jsonParser, listNode.get(s)))
);
return new Response(elements);
}
private Element parseElement(JsonParser jsonParser, TreeNode subNode) {
Element element = null;
try {
element = jsonParser.getCodec().treeToValue(subNode, Element.class);
} catch (JsonProcessingException e) {
e.printStackTrace(); //TODO handle it in a better way
}
return element;
}
}
I have an object like
#JsonDeserialize(using = CustomConfigDetailDeserializer.class)
HashMap<String,HashMap<String,HashMap<String, ConfigDetail>>> entityConfig;
Here ConfigDetail is an Interface.
I wish to have Custom Serialiser and Deserialiser for the above object.
The deserializer I have written does something like this:
public class CustomConfigDetailDeserializer extends JsonDeserializer<ConfigDetail> {
#Override
public ConfigDetail deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
for(Class clazz: ConfigDetailConstant.IMPLEMENTATIONS) {
try {
return (ConfigDetail)jsonParser.readValueAs(clazz);
} catch (Exception e) { }
}
throw new InvalidDataException("Unrecognised Config Detail found for date " + jsonParser.getText());
}
}
The IMPLEMANTATIONS set referenced above goes something like this:
public static class ConfigDetailConstant{
static HashMap<String,HashMap<String, HashMap<String, DummyConfig>>> dummyConfig;
static HashMap<String,HashMap<String,HashMap<String, NotificationConfig>>> notificationConfig;
public static final Set<Class> IMPLEMENTATIONS = Sets.newHashSet(dummyConfig.getClass(),notificationConfig.getClass());
}
The class type of this is being returned as NULL.
I am confused on how to correctly do this.
Any Help is appreciated.
The JSON I wish to Convert to a POJO:
"entity_configuration": {
"entity_config": {
"INVOICE": {
"default": {
"notification_config": {
"state": ["CREATED", "COMPLETED"],
"notification_type": ["email", "sms"]
},
"dummy_config": {
"name": ["ABC", "HELLO"]
}
},
"REGULAR": {
"notification_config": {
"state": ["COMPLETED"],
"notification_type": ["email"]
}
}
},
"VARIANCE": {
"default": {
"notification_config": {
"state": ["CREATED", "COMPLETED"],
"notification_type": ["sms"]
}
}
}
}
}
Thanks in Advance!
I am trying to apply Lifecycle Configurations on S3 bucket. Trying to apply using following JSON:
[{
"id": "tmpdelete",
"status": "Enabled",
"filter": {
"predicate": {
"prefix": "tmp"
}
},
"transitions": [{
"days": "1",
"storageClass": "GLACIER"
}],
"noncurrentVersionTransitions": [{
"days": "1",
"storageClass": "GLACIER"
}],
"expirationInDays": "2",
"noncurrentVersionExpirationInDays": "2",
"expiredObjectDeleteMarker": "true"
}]
When i am trying to map it with Rule[].class it is not working. I am using following code:
String json = above_json;
Rule[] rules = null;
Gson gson = new GsonBuilder().serializeNulls().excludeFieldsWithModifiers(Modifier.FINAL,
Modifier.TRANSIENT, Modifier.STATIC, Modifier.ABSTRACT).create();
rules = gson.fromJson(json, Rule[].class);
try {
amazonS3.setBucketLifecycleConfiguration(bucketName, new BucketLifecycleConfiguration().withRules(rules));
} catch (Exception e) {
throw e;
}
It throws error saying Failed to invoke public com.amazonaws.services.s3.model.lifecycle.LifecycleFilterPredicate() with no args. LifecycleFilterPredicate is an abstract class which implements Serializable and it doesn't have no-args contructor. How to solve this problem.?
Ok, I think I found your problem: when GSON tries to construct the objects from that json string into an actual object (or, in this case, a list of objects), the process fails because when it gets to the filter.predicate bit, it probably tries to do something like this:
LifecycleFilterPredicate predicate = new LifecycleFilterPredicate();
predicate.setPrefix("tmp");
Which doesn't work because LifecycleFilterPredicate doesn't have a public constructor without any arguments, as you've stated.
I think that, unfortunately, your only solution is to parse the JSON in a different way.
UPDATE
You'll need to make use of a GSON TypeAdapter as follows:
class LifecycleFilterPredicateAdapter extends TypeAdapter<LifecycleFilterPredicate>
{
#Override
public LifecycleFilterPredicate read(JsonReader reader)
throws IOException
{
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
reader.beginObject();
if(!"prefix".equals(reader.nextName()))
{
return null;
}
String prefix = reader.nextString();
LifecyclePrefixPredicate predicate = new LifecyclePrefixPredicate(prefix);
reader.endObject();
return predicate;
}
#Override
public void write(JsonWriter writer, LifecycleFilterPredicate predicate)
throws IOException
{
//nothing here
}
}
...
Gson gson = new GsonBuilder().serializeNulls().excludeFieldsWithModifiers(Modifier.FINAL,
Modifier.TRANSIENT, Modifier.STATIC, Modifier.ABSTRACT)
.registerTypeAdapter(LifecycleFilterPredicate.class, new LifecycleFilterPredicateAdapter()).create();
I've tried it locally and don't get the exception anymore :)
I tried this and it worked for me
public class RuleInstanceCreator implements InstanceCreator<LifecycleFilterPredicate> {
#Override
public LifecycleFilterPredicate createInstance(Type type) {
return new LifecycleFilterPredicate() {
private static final long serialVersionUID = 1L;
#Override
public void accept(LifecyclePredicateVisitor lifecyclePredicateVisitor) {
}
};
}
}
Gson gson = new GsonBuilder()
.registerTypeAdapter(LifecycleFilterPredicate.class, new LifecycleFilterPredicateAdapter()).create();
rules = gson.fromJson(json, Rule[].class);
In our project we parse JSON with Jackson. We set field saved by field channelId. Problem is that channelId field is parsed later than saved. So at the time we want to set field saved field channelId is null. How we can set field dependency in JSON deserialization, so field saved will be set after channelId?
This is part of our JSON data:
"message":{
"data":{
"text":"Some text"
},
"saved_by":[
2715,
1234
],
"some_boolean_field":false,
"channel_id":8162
}
This is our entity class:
#JsonIgnoreProperties(ignoreUnknown = true)
#org.parceler.Parcel(org.parceler.Parcel.Serialization.BEAN)
public class Message {
#JsonProperty("channel_id")
protected long channelId;
protected boolean saved;
#JsonSetter("saved_by")
public void setSavedBy(Set<Long> savedBy) {
saved = savedBy.contains(getUserIdByChannelId(channelId));
}
public long getChannelId() {
return channelId;
}
public void setChannelId(long channelId) {
this.channelId = channelId;
}
public boolean isSaved() {
return saved;
}
public void setSaved(boolean saved) {
this.saved = saved;
}
public void setData(JsonNode data) throws JsonProcessingException {
JsonNode textNode = data.get("text");
text = textNode != null ? textNode.asText() : "";
components = new ArrayList<>();
JsonNode mediaNode = data.get("media");
if (mediaNode != null) {
MessageComponent[] parsedComponents = AppSession.getInstance().getObjectMapper().treeToValue(mediaNode, MessageComponent[].class);
List<MessageComponent> components = Arrays.asList(parsedComponents).subList(0, parsedComponents.length < 4 ? parsedComponents.length : 4);
this.components.addAll(components);
}
mediaCount = components.size();
}
}
Full JSON:
{
"data":{
"serial":66,
"updated_entity":"bookmark",
"bookmark":{
"message":{
"data":{
"text":"hello"
},
"counted_serial":748,
"saved_by":[
26526,
27758
],
"type":"UserMessage",
"is_reviewed":false,
"channel_id":8128,
"id":2841531,
"replied_message_data":null,
"is_blocked":false,
"is_deleted":false,
"updated_at":"2016-11-21T05:59:52.471Z",
"spam_reported_by":[
],
"created_at":"2016-11-19T15:40:17.027Z",
"uuid":"0b6ba58e-f5e1-4ee5-a9da-041dfc2c85cd",
"liked_by":[
],
"user":{
"last_name":"M",
"id":4537,
"first_name":"John",
"is_deleted":false,
"avatar_thumb":"https:\/\/cdn.site.org\/uploads\/99ef4d68-6eaf-4ba6-aafa-74d1cf895d71\/thumb.jpg"
},
"serial":934
},
"id":6931,
"created_at":"2016-11-21T05:59:52.459Z",
"is_deleted":false,
"updated_at":"2016-11-21T05:59:52.459Z"
}
},
"type":"action_performed"
}
It's a bit hackish, but by making the Message class its own deserialization-builder, you get a kind of "ready for bean creation"-event in which you have access to all of the properties.
My suggestion is that you try the following :
#JsonDeserialize(builder = Message.class)
public class Message {
...
#JsonSetter("saved_by")
public void setSavedBy(Set<Long> savedBy) {
// Merely store the value for later use.
this.savedBy = savedBy;
}
...
public Message build() {
// Calculate value of "saved" field.
this.saved = this.savedBy.contains(getUserIdByChannelId(this.channelId));
return this;
}
// Handling the added challenge.
#JsonProperty("data")
public void setData(JsonNode data) throws JsonProcessingException {
...
}
}
The above takes advantage of the default settings of the JsonPOJOBuilder annotation, namely that the default value for buildMethodName is build.
I have two different Json responses(having different keys) generated out of two different requests :
Response 1 :
{
"response": {
"count": 2,
"programs": [
{
"title": "xyz1",
"desc": "ABCDEF1"
},
{
"title": "xyz2",
"desc": "ABCDEF2"
}
]
}
}
Response 2
{
"response": {
"count": 3,
"shows": [
{
"name": "PQR1",
"desc": "qwerty1"
},
{
"name": "PQR2",
"desc": "qwerty2"
},
{
"name": "PQR3",
"desc": "qwerty3"
}
]
}
}
As we can see the responses contain data with different keys. But Ultimately It could be transformed into (Array of) same Java object like this one:
Program {
String title;
int description;
}
I want to write single parsing logic that handles different key names and return Program list. How to achieve this efficiently?
Is there any library available to conveniently do this ?
You may choose the field in the getter when deserialized both of them (example works with GSON):
class Program {
private String title, name;
#SerializedName("desc") private String description;
private String getTitle() {
return title == null ? name : title;
}
// other getters, empty constructor and so on...
}
Also (again GSON), you can register your own TypeAdapter when creating Gson object.
// let Program have empty constructor (or no constructors at all), getters and setters
class ProgramAdapter extends TypeAdapter<Program> {
#Override
public Program read(final JsonReader in) throws IOException {
final Program obj = new Program();
in.beginObject();
while (in.hasNext()) {
String jsonTag = in.nextName();
if ("desc".equals(jsonTag)) {
obj.setDescription(in.nextString());
} else if ("title".equals(jsonTag)
|| "name".equals(jsonTag)) {
obj.setTitle(in.nextString());
}
}
in.endObject();
return obj;
}
#Override
public void write(final JsonWriter out, final Program obj)
throws IOException {
out.beginObject();
out.name("title").value(obj.getTitle());
out.name("desc").value(obj.getDescription());
out.endObject();
}
}
// then, when create `Gson` object:
Gson gson = new GsonBuilder().registerTypeAdapter(Program.class, new ProgramAdapter()).create();