For JSON like this, Jackson provides us with a way to cope with this. The solution doesn't seem to work well with case classes, and seems to require a good deal more annotations that it should, but it does get the job done in a none too egregious way.
Here's a sample of bad JSON:
[{ "startDate" : "2010-01-01", "city" : "Las Vegas", "channel": "Alpha" },{ "startDate" : "2010-02-01", "city": "Tucson", "channel": ["Alpha","Beta"] }]
You can see that in the first element, the field 'channel' is supplied as a string, and in the second, it's now a list. If you set the type of your field to List[String] in Scala, it will throw an error when deserializing a plain String rather than just converting it to a single element list. I understand why it's a good idea for deserialization to do this, but really, if you're using JSON, then schema compliance probably isn't at the top of the list of requirements.
You can deal with this using the JsonAnySetter annotation. Unfortunately, once you use this, it seems all hell breaks loose and you must then use JsonProperty on everything and it's brother. The method that you defined annotation by JsonAnySetter will accept two arguments that function as a key value pair. The key and value will be typed appropriately, so the key is always a String, and the value will be whatever type deserialization found most appropriate. In this case, it will be a String or an java.util.ArrayList. We can disambiguate these types with a case match construct, which for this seems perfect:
@BeanInfo class Data(@BeanProperty @JsonProperty("startDate") var startDate: String, @BeanProperty @JsonProperty("city") var city: String, @BeanProperty @JsonIgnore("channel") var channel: List[String]) { // No argument constructor more or less needed for Jackson def Data() = this("", "", Nil) @JsonAnySetter def setter(key: String, value: Any) = { key match { case "channel" => { value match { case s: String => { channel = List(s) } case l: java.util.ArrayList[String] => { channel = Range(0,l.size()).map(l.get(_)).toList } case _ => { // No-op if you're ignoring it, or exception if not } } } } } }
Now when the bad JSON gets passed into deserialization it will get mapped more smartly than it was generated, and we win!
I might have a poke at it to see if I can get it working with less additional annotation crazy too.
No comments:
Post a Comment