0

I'm trying to deserialize json to a java entity, but I'm having trouble getting Jackson to deserialize an json array to a Pair[]. Pair is a custom class but it follows the standard form found on SO :

public class Pair<K, V> {

private final K k;
private final V v;

public Pair(K k, V v) {
    this.k = k;
    this.v = v;
}

public K getKey() {
    return k;
}

public V getValue() {
    return v;
}
...
}

The Json object can be changed as needed, but I'm trying to use something of the form:

{"priority":1,"account":"012345","tld":"com","name":"domain_check","params":[{"key":"domain","value":"domain.com"}]}

My "DefaultRequest" entity class contains:

@Entity
public class DefaultRequest implements Serializable, Comparable, Request {

//other fields...

private Pair[] params;

@Override
public Pair[] getParams() {
    return params;
}

@Override
public void setParams(Pair[] params) {
    this.params = params;

//other methods...
}
}

I'm sorry if someone has answered this already. I'm admittedly new to using jackson, but I've spent almost 3 days on this and I have a deadline. I also have to believe someone has had to deserialize to an entity containing a Pair[] before. Any ideas are welcome. I have complete flexibility in changing both the json format and the entity class if there is a better alternative. Thanks in advance!

Community
  • 1
  • 1
jasondt
  • 33
  • 5
  • Are you also doing the serialization? I mean are you the one producing the json by serializing some of your objects and then want to deserialize back? – eugen Oct 03 '12 at 20:03

2 Answers2

1

Question is bit vague, so I might be wrong, but you may be looking for the annotation to tell Jackson how to use a custom constructor to pass data. If so:

@JsonCreator
public Pair(@JsonProperty("key") K k,
   @JsonProperty("value") V v) {
    ...
}

This will work for all kinds of types, not just primitives, nor does it try to guess what goes where. Annotations for names are needed only because JDK does not add argument names in bytecode, so they are not available, unlike method names.

EDIT:

Looks like a transformation is needed. So how about this:

@JsonCreator
public Pair(Map<String,String> json)
{
   Map.Entry<String,String> en = json.entrySet().iterator().next();
   key = en.getKey();
   value = en.getValue();
}

@JsonValue
public Map<String,String> asMap() {
  Map<String,String> m = new HashMap<String,String>();
  m.put(key, value);
  return m;
}

Bit more code, but allows conversions to intermediate types. For what it's worth, Jackson 2.1 will include standard "delegating" serializer/deserializer, to make it possible to define a Converter that encapsulates such logic.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • you're not wrong. you're a genius. thank you. My test code deserializes just fine. I'll throw some more commands at it tomorrow just to be sure, but I think that's the solution. – jasondt Oct 03 '12 at 21:12
  • This works for all kind of types when DefaultRequest contains a untyped pair array? I'd be curious to see that... – eugen Oct 03 '12 at 21:17
  • actually, I may have spoke too soon. This serializes/deserializes properly, but its actually storing the key and value as separate pairs in the array (ie [Key = domain, Value = domain.com] and I'm looking to just get [domain = domain.com] if that makes sense). You definitely have me on the right track though I believe. I'll keep working and see if I can figure it out. – jasondt Oct 03 '12 at 21:37
  • @eugen I just mean that it is not limited to scalar values. As to generic types, it all depends on whether they are resolvable. If they are, yes, it should work as expected. – StaxMan Oct 03 '12 at 22:19
  • @jasondt There is no way to transform values directly that way; custom (de)serializers would be the way to go. – StaxMan Oct 03 '12 at 22:22
  • alright, thanks @StaxMan. I was trying to avoid writing a customer deserializer as this is my first attempt at using json in java, but it might come to that. My other thought was to deserialize node by not and use another class in between. That's kind of what I'm doing now though. I'm just deserializing to a map first then iterating over the map to write matching fields to the entity then dumping whatever is left in the params array. it just seems like a terrible horrible no good very bad hack. thought there might be a simple way /shrug. Ill try tomorrow and accept your answer if nothing else. – jasondt Oct 03 '12 at 22:47
  • Ah. If you do control `Pair`, I do have one more idea. `@JsonValue` allows you to convert `Pair` into, say, `Map` or `ObjectNode`, return that, and Jackson will serialize it. And `@JsonCreator` on a single-arg constructor, where that argument does NOT take `@JsonProperty` allows you to delegate. – StaxMan Oct 03 '12 at 23:09
  • @StaxMan In that case there is no advantage over Genson, Genson supports generic types as well and it can even deserialize to unknown/abstract types. You also say "nor does it try to guess what goes where" like it is something great, but in fact it is not, you require people to put extra annotations on their code because you can not resolve the parameter names. Genson in addition of the annotation technique, puts user first and implements a handy technique that without any code it will use the right parameters with the right type. I don't see any reason for jasondt to use Jackson here... – eugen Oct 04 '12 at 10:00
  • Yes, I do think it is better to be explicit than take a guess when there's good chance to get it wrong. Since there are no names, and properties have no order in general (whereas ctor params are ordered), I think this is wrong thing to do; as simple as possible, and no simpler. Different philosophy here. As to "you can not resolve"... as you should know, it is a limitation by platform. Names are not store in byte code. – StaxMan Oct 04 '12 at 21:23
  • I appreciate the help, but it looks like I'll just stick to my original idea of writing the json to a map and setting the values from there. While this may not be impossible, it seems like its far more trouble than its worth. I honestly didn't try your edited solution @StaxMan. It seems like it has potential, but using two maps in between seems worse than using the one. I decided to drop the entity restraint as well. I'll just write out my database calls myself and use a map instead of the pair [] so even the initial map i use gets copied directly to the request. Seems like the simplest idea. – jasondt Oct 04 '12 at 23:29
  • @Staxman Sure being explicit is nice, but in this precise case if it was easy to get parameter names every library would do it. If people don't do it is because many don't know how. In fact you can get parameter names if classes are compiled with debug information (its true around 80%) and this feature is disabled by default in Genson, so user enable it **explicitly** and get best of all worlds :) – eugen Oct 05 '12 at 12:03
  • Ok! Would be interested in digging this up from debug info (I assumed an external lib was needed as per other comments), or using paranamer. But maybe later. Thanks again for interesting discussion! – StaxMan Oct 05 '12 at 20:46
  • @jasondt No prob, whatever works; and changing JSON makes sense. My solution simply pointed out how `Pair` could indeed be converted from that struct, by using existing `Map` handler. – StaxMan Oct 05 '12 at 20:48
0

Using Genson library http://code.google.com/p/genson/ it works fine.

You have to rename the parameters of your constructor to key and value and then:

// this will tell to genson that he can use special debug symbols 
// to guess the constructor parameters :)    
Genson genson = new Genson.Builder().setWithDebugInfoPropertyNameResolver(true).create();
DefaultRequest req = genson.deserialize(json, DefaultRequest.class);

This works if your key type is a string and the value a primitive type (or a primite wrapper), if you want to have your value as complex objects then its a bit more complicated. It works if you are the one that produced the json. Here is a link on how to achieve this with Genson.

eugen
  • 5,856
  • 2
  • 29
  • 26
  • Thanks for the idea. Everything seems to be fine with jackson now though based on staxman's suggestion. – jasondt Oct 03 '12 at 21:15
  • If you want to use annotations instead of letting Genson do the job you can also use the @JsonProperty("thename") on the constructor parameters (thats why I told you to change their names...). This will work without needing to configure Genson. In fact Genson provides you the best of both worlds, if you have control over the classes you can add annotations but if you don't Genson always provides another way to do that – eugen Oct 03 '12 at 21:21
  • I'm looking again, but I still fail to see how Genson will be any different than Jackson. The use of annotations is only because it seems to be required when using generics. I'm using an array of pairs because I can't use a map in the entity, but essentially that's the functionality I'm looking for. I'm really just struggling because I am trying to take a Json array ["key" : "k", "value" : "v"] and deserialize to just one pair. I just don't see how genson is any different than jackson in this respect. Am I missing something? – jasondt Oct 03 '12 at 22:23
  • no it is not required because of generics but because Jackson can not resolve the name of the parameters, Genson does! See my answer to Staxman for other reasons to use Genson over Jackson. What do you want to achieve deserialize to a map having "domain" as key and "domain.com" as value? Or put these values in k and v of Pair object? – eugen Oct 04 '12 at 10:05
  • I would like to put domain and domain.com in the pair object as the key and value respectively. Right now, jackson is placing 2 pairs into the array. one with and the other with instead of just . Genson didn't seem to be any different unless Im missing something. I actually did download it and run the same tests and got the same results. I do have to admit genson is nice in that it only has one library and simpler api. – jasondt Oct 04 '12 at 20:30
  • By running the example I showed you (rename the constructor params to key and value or add the JsonProperty("key"), "value" annotation) I got a params array with a single Pair object, k="domain" and v="domain.com" is it what you are expecting? – eugen Oct 04 '12 at 20:48
  • I did but I got the same results as with jackson or an error depending on the annotations. I'm done with it though. I gave up on trying to use an entity and plugged in a map in its place. I'm sending all the values in one single json object without using an array and deserializing to a jsonMap. then I remove the values of tld, name, account, and priority that are known and then set the jsonMap to the the params map in the request class. I'm sure there is a solution to this, but in my case at least its more trouble than its worth. But I will try your solution out of curiosity if I get a chance. – jasondt Oct 05 '12 at 15:34
  • Oh, you may be happy to know I have started using genson in place of jackson. Ill have to run some speed tests to be sure Ill keep it, but its definitely easier to use. Thanks for the tip @eugen! – jasondt Oct 05 '12 at 15:35
  • Ha great :) Here are some benchmarks of Genson http://code.google.com/p/genson/wiki/Metrics2, concerning speed Jackson is slightly faster but not in all cases, for example using Gson benchmarks Genson is the fastest for deserialization, so it depends a bit on the data. However concerning speed they both a really fast so there other things to take into account (like ease to use), plus genson jar is 5 times smaller ;) – eugen Oct 05 '12 at 15:47