I have the following json
{
"id":null,
"name":"Myapp",
"description":"application",
"myListA":["java.util.ArrayList",[{
"id":50,
"name":"nameA1",
"myListB":{
"id":48,
"name":"nameB1",
"myListC":["java.util.ArrayList",[{
"id":1250,
"name":"nameC1",
"description":"nameC1_desc",
"myReferenceObject":{
"code":"someCodeA"
}
},{
"id":1251,
"name":"nameC2",
"description":"nameC1_desc",
"myReferenceObject":{
"code":"someCodeB"
}
and so on.
I i wish to replace myReferenceObject with an item from persistence layer.
I followed JacksonHowToCustomDeserializers
My deserializer is as follows:
public class MyReferenceObjectCodeDeserializer extends JsonDeserializer<MyReferenceObjectBean> {
#Override
public ColumnReferenceBean deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
while (jp.nextToken() != JsonToken.END_OBJECT) {
String fieldname = jp.getCurrentName();
jp.nextToken();
if ("code".equalsIgnoreCase(fieldname)) {
MyReferenceObjectBean b = MyReferenceObjectServiceImpl.retrieveByCode(jp.getText());
logger.info("returning " +b.toString());
return b;
}
}
logger.info("returning null");
return null;
}
}
And I attache the module like so:
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
SimpleModule module = new SimpleModule("myModule", new Version(1, 0, 0, null));
module.addDeserializer(MyReferenceObjectBean.class, new MyReferenceObjectCodeDeserializer());
mapper.registerModule(module);
try {
return mapper.readValue(serializedJsonString, MyMainObjectBean.class);
} catch (IOException e) {
logger.error("Unable to parse=" + serializedJsonString, e);
}
everything debugs correctly however the resulting myListC list has double the amount of objects with even numbers holding the correct objects along with the correct myReferenceObject out of persistence (deserialized correctly using my module) and the odd elements holding empty Pojos, that is an object with null values for all variables.
Through debug, It never reaches return null in my custom deserializer, for it works properly each time its executed. The issue seems to be further up stream where it inserts blank myListC objects.
Any help would greeatly be appreciated.
Thanks!
There is a logic problem in your code.
You want to loop until you reach the end of the object but break your loop with return b (if block). This means that you will not read the object stream until its end.
Try something like this (didn't try it but should work).
public class MyReferenceObjectCodeDeserializer extends JsonDeserializer<MyReferenceObjectBean> {
#Override
public ColumnReferenceBean deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
MyReferenceObjectBean b = null;
while (jp.nextToken() != JsonToken.END_OBJECT) {
String fieldname = jp.getCurrentName();
jp.nextToken();
if ("code".equalsIgnoreCase(fieldname)) {
b = MyReferenceObjectServiceImpl.retrieveByCode(jp.getText());
logger.info("returning " +b.toString());
}
}
if (b==null) logger.info("returning null");
return b;
}
}
You can also have a look at Genson http://code.google.com/p/genson/ if you can change from jackson. In addition of some other features it is supposed to be easier to use. Here is how you can solve your problem with genson (for this example it is quite similar to jackson):
public class MyReferenceObjectCodeDeserializer implements Deserializer<MyReferenceObjectBean> {
public MyReferenceObjectBeandeserialize(ObjectReader reader, Context ctx) throws TransformationException, IOException {
MyReferenceObjectBean b = null;
reader.beginObject();
while (reader.hasNext()) {
reader.next();
if ("code".equalsIgnoreCase(reader.name()))
b = MyReferenceObjectServiceImpl.retrieveByCode(reader.valueAsString());
}
reader.endObject();
return b;
}
}
// register it
Genson genson = new Genson.Builder().withDeserializers(new MyReferenceObjectCodeDeserializer()).create();
MyClass myClass = genson.deserialize(json, MyClass.class);
Related
I have a method which does the following:
final DaySerializer daySerializer = new DaySerializer(Day.class);
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule("DaySerializer", new Version(2, 1, 3, null, null, null));
module.addSerializer(Day.class, daySerializer);
mapper.registerModule(module);
try {
mapper.writerWithDefaultPrettyPrinter().writeValue(new File(this.jsonPath + "/Days.json"), days);
} catch (final Exception e) {
e.printStackTrace();
}
And the serializer class method:
#Override
public void serialize(final Day day, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider)
throws IOException {
jsonGenerator.writeStartObject();
//Date
SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd");
jsonGenerator.writeStringField("calendar", "" + format1.format(day.getCalendar().getTime()));
//Day index
jsonGenerator.writeNumberField("dayCount", day.getDayCount());
//Demand
for (final Entry<String, int[]> e : day.getDemand().entrySet()) {
jsonGenerator.writeFieldName("Demand: "+e.getKey());
jsonGenerator.writeStartArray();
for(int i : e.getValue()) {
jsonGenerator.writeNumber(i);
}
jsonGenerator.writeEndArray();
}
//Employee shift allocations
for(EmployeeShiftAllocation eSA : day.getEmployeeShiftAllocations()) {
jsonGenerator.writeNumberField("eSA ID", eSA.getId());
}
jsonGenerator.writeEndObject();
}
Which works for the first Day object in the List which is handed in to it, but doesn't show any errors, but only exports the first Day object. This is confusing since I do hand in the List. Am I missing something simple? Do I need to handle this inside the serializer somehow?
I have a problem with deserialization of the JSON using Jackson in my rest-assured tests.
In my JSON I have a key "value" that can be an array of Strings or object like Boolean.
{
"value": ["value1", "value2"]
}
or
{
"value": 2272204.2426
}
So I wrote custom deserializer for this field:
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
if (node.isArray()) {
List<String> list = new ArrayList<>();
for (JsonNode elementNode : node) {
list.add(oc.treeToValue(elementNode, String.class));
}
return list;
} else {
if(node.isDouble()) {
return oc.treeToValue(node, Double.class);
}
else if(node.isBoolean()){
return oc.treeToValue(node, Boolean.class);
}
else {
return oc.treeToValue(node, String.class);
}
}
}
In the end I've noticed that numeric value like 2272204.2426 is deserialized to 2272204.2
I tried to desierialize it using Gson and it works well. Do you have any idea why using Jackson there is lack of decimal part?
I've tried to debug the code and I've noticed that on this step JsonNode node = oc.readTree(jp); the value is 2272204.2
Why not use ObjectMapper from Jackson? You can add DeserializationFeature to it unlike ObjectCodec. Mapper is actually extending Codec, but with more features that you need in this case.
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
JsonNode node = //node where the value is defined as Double
Double value = null;
try {
value = mapper.treeToValue(node, Double.class);
}
catch (IOException e) {
e.printStackTrace();
}
System.out.println(value);
Use the above logic in your node.isDouble() case
Ok so i have two classes: Content and TrackUserChanges . They have the same fields.
public class Content{
private Long id;
......
private SortedSet<Content> children = new TreeSet<Content>();
}
public class TrackUserChanges{
private Long id;
.....
private SortedSet<Content> children = new TreeSet<Content>();
}
I want to clone the data from Content to TrackUserChanges:
I have the children from an instance of content:
SortedSet<Content> children = content.getChildren();
This can contain many other contents, and that contents can have also children, etc.
SortedSet<TrackUserChanges> children1 = convertContentSetToTrackUserChangesSet(children);
This is the method:
public SortedSet<TrackUserChanges> convertContentSetToTrackUserChangesSet(SortedSet<Content> children){
SortedSet<TrackUserChanges> children1 = new TreeSet<TrackUserChanges>();
for(Content c : children){
TrackUserChanges trackU = new TrackUserChanges();
trackU.setCategory(c.getCategory());
trackU.setId(c.getId());
trackU.setBook(c.getBook());
trackU.setInsertUser(c.getInsertUser());
trackU.setParent(c.getParent());
trackU.setParentId(c.getParentId());
trackU.setRelativeSortOrder(c.getRelativeSortOrder());
trackU.setText(c.getText());
trackU.setType(c.getType());
children1.add(trackU);
}
return children1;
}
I need somehow to call recusively or something like that... and I can't do trackU.setChildren(c.getChildren()) because the types don't match :/
#Mifmif
try {
FileOutputStream fout = new FileOutputStream("a.dat");
ObjectOutputStream oos = new ObjectOutputStream(fout);
oos.writeObject(content);
oos.close();
}
catch (Exception e) { e.printStackTrace(); }
TrackUserChanges trackUserChanges11 = new TrackUserChanges();
try {
FileInputStream fin = new FileInputStream("a.dat");
MyCustomObjectInputStream custom = new MyCustomObjectInputStream(fin);
// custom.readClassDescriptor();
trackUserChanges11 = (TrackUserChanges) custom.readObject();
System.out.println("IDDDDD" + trackUserChanges11.getId());
custom.close();
}
catch (Exception e) { e.printStackTrace(); }
ClassCastException :)
If you have two classes with identical properties that have identical behaviour, separate those fields/behaviour into a separate class and extend that class in your other classes. This should allow you to copy directly between them.
Make your class implements Cloneable. Then just call the function clone of the SortedSet.
http://docs.oracle.com/javase/7/docs/api/java/lang/Cloneable.html
Here is a solution based on serialization , make both of your class serializable and if you want to go from one class instance to another, serialize your instance into a file , and read it using MyCustomObjectInputStream :
class MyCustomObjectInputStream extends ObjectInputStream {
public MyCustomObjectInputStream(InputStream in) throws IOException {
super(in);
}
#Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();
if (resultClassDescriptor.getName().equals("name.of.the.package.OldClassName"))
resultClassDescriptor = ObjectStreamClass.lookup(name.of.the.package.NewClassName.class);
return resultClassDescriptor;
}
}
I resolved the problem
public TrackUserChanges convertContentToTrack(Content c) {
TrackUserChanges t = new TrackUserChanges();
t.setCategory(c.getCategory());
t.setId(c.getId());
t.setBook(c.getBook());
t.setInsertUser(c.getInsertUser());
t.setParent(c.getParent());
t.setParentId(c.getParentId());
t.setRelativeSortOrder(c.getRelativeSortOrder());
t.setText(c.getText());
t.setType(c.getType());
if (c.getChildren().size() == 0) {
return t;
}
SortedSet<TrackUserChanges> childs = new TreeSet<TrackUserChanges>();
for (Content content : c.getChildren()) {
childs.add(convertContentToTrack(content));
}
t.setChildren(childs);
return t;
}
Thanks anyway :)
I'm writing a typical Play Framework app where I want to return a JsonNode from my Controller's methods, using Jackson.
This is how I'm doing it right now:
public static Result foo() {
MyPojoType myPojo = new myPojo();
String tmp = new ObjectMapper().writerWithView(JSONViews.Public.class).writeValueAsString(myPojo);
JsonNode jsonNode = Json.parse(tmp);
return ok(jsonNode);
}
Is it possible to avoid the "String tmp" copy and convert directly from MyPojoType to JsonNode using a view?
Maybe I can use ObjectMapper.valueToTree, but I don't know how to specify a JSonView to it.
Interesting question: off-hand, I don't think there is a specific method, and your code is the most straight-forward way to do it: valueToTree method does not apply any views.
So code is fine as is.
After more investigation, this is what I did in the end to avoid the redundant work:
public Result toResult() {
Content ret = null;
try {
final String jsonpayload = new ObjectMapper().writerWithView(JsonViews.Public.class).writeValueAsString(payload);
ret = new Content() {
#Override public String body() { return jsonpayload; }
#Override public String contentType() { return "application/json"; }
};
} catch (JsonProcessingException exc) {
Logger.error("toResult: ", exc);
}
if (ret == null)
return Results.badRequest();
return Results.ok(ret);
}
In summary: The methods ok, badRequest, etc accept a play.mvc.Content class. Then, simply use it to wrap your serialized json object.
As i know, with jax-rs, you can do this :
public Response toResult() throws JsonProcessingException {
final ObjectWriter writer = new ObjectMapper()
.writerWithView(JSONViews.Public.class);
return Response.ok(new StreamingOutput() {
#Override
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
writer.writeValue(outputStream, /*Pojo*/ payload);
}
}).build();
}
So you have to find a class in the Play framework which able to stream the result (through an OutputStream)
I think this is more efficient way
public Result toResult() {
MyPojo result = new MyPojo();
JsonNode node = objectMapper.valueToTree(result);
return ok(node);
}
The webservice returns an empty string instead of NULL which causes Jackson to crash.
So I created a custom parser, and I'm trying to parse it manually? Any idea How I could achieve this?
What Am I doing wrong here? All I'm trying to do is to parse JSON to object as I normally would. The field names are added to my properties using #JsonProperty so the parser should know how to convert it.
public class InsertReplyDeserializer extends JsonDeserializer<ListingReply> {
#Override
public ListingReply deserialize(JsonParser jsonParser, DeserializationContext arg1)
throws IOException, JsonProcessingException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
// If service returns "" instead of null return a NULL object and don't try to parse
if (node.getValueAsText() == "")
return null;
ObjectMapper objectMapper = new ObjectMapper();
ListingReply listingReply = objectMapper.readValue(node, ListingReply.class);
return listingReply;
}
}
Here is how I resolved it
#Override
public MyObject deserialize(JsonParser jsonParser, DeserializationContext arg1)
throws IOException, JsonProcessingException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
if (node.getValueAsText() == "")
return null;
MyObject myObject = new MyObject();
myObject.setMyStirng(node.get("myString").getTextValue());
JsonNode childNode = node.get("childObject");
ObjectMapper objectMapper = new ObjectMapper();
ChildObject childObject = objectMapper.readValue(childNode,
ChildObject.class);
myObject.setChildObject(childObject);
return myObject;
}
I am not sure you need to manually parse response. You solution would work but seems sub-optimal in my opinion. Since it looks like that you are using RestTemplate, you should rather write (or move your parser code to) your own message converter. Then add this converter to your rest template object which will internally deserialize the value for you. Something along the lines,
public class CustomHttpmsgConverter extends AbstractHttpMessageConverter<Object> {
private ObjectMapper objectMapper = new ObjectMapper();
#Override
protected Object readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
InputStream istream = inputMessage.getBody();
String responseString = IOUtils.toString(istream);
if(responseString.isEmpty()) //if your response is empty
return null;
JavaType javaType = getJavaType(clazz);
try {
return this.objectMapper.readValue(responseString, javaType);
} catch (Exception ex) {
throw new HttpMessageNotReadableException(responseString);
}
}
//add this converter to your resttemplate
RestTemplate template = new RestTemplate();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new CustomHttpmsgConverter());
template.setMessageConverters(converters);