Is it possible to deserialize JSON using Jackson into one of two types based on the content of the JSON?
For example, I have the following Java (technically Groovy, but that's not important) interfaces and classes:
interface Id {
Thing toThing()
}
class NaturalId implements Id {
final String packageId
final String thingId
Thing toThing() {
new PackageIdentifiedThing(packageId, thingId)
}
}
class AlternateId implements Id {
final String key
Thing toThing() {
new AlternatelyIdentifiedThing(key)
}
}
The JSON I will receive will look like either of the following:
This JSON should map to NaturalId {"packageId": "SomePackage", "thingId": "SomeEntity"}
This JSON should map to AlternateId {"key": "SomeUniqueKey"}
Does anyone know how I can accomplish this with Jackson 2.x WITHOUT including type id's?
Are these the only two classes that implement Id? If so, you could write an IdDeserializer class and put #JsonDeserialize(using = IdDeserializer.class) on the Id interface, and the deserializer would look at the JSON and determine which object to deserialize into.
EDIT: The JsonParser is streaming so it should look something like this:
public Id deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectNode node = jp.readValueAsTree();
Class<? extends Id> concreteType = determineConcreteType(node); //Implement
return jp.getCodec().treeToValue(node, concreteType);
}
Annotate your methods with #JsonIgnore
#JsonIgnore
Thing toThing() {
new PackageIdentifiedThing(packageId, thingId)
}
With Jackson2, you can easily marshall to different classes using generics:
private <T> T json2Object(String jsonString, String type, Class<T> clazz) {
JsonNode jsonObjectNode = getChildNode(jsonString, type);
T typeObject = null;
try {
typeObject = jacksonMapper.treeToValue(jsonObjectNode, clazz);
} catch (JsonProcessingException jsonProcessingException) {
LOGGER.severe(jsonProcessingException);
}
return typeObject;
}
Related
I have a POJO that contains the following attributes
public class Example {
#JsonProperty("inputFoo")
private String foo
#JsonProperty("inputBar")
private String bar
#JsonProperty("inputBaz")
#JsonDeserialize(using = MyDeserializer.class)
private Set<String> baz
}
The JSON that I am working with to represent this data currently represents the baz attribute as a single string:
{"inputFoo":"a", "inputBar":"b", "inputBaz":"c"}
I am using the Jackson ObjectMapper to attempt to convert the JSON to my POJO. I know that the input baz String from the JSON wont map cleanly to the Set that I am trying to represent it as, so I defined a custom Deserializer:
public class MyDeserializer extends StdDeserializer<Set<String>> {
public MyDeserializer(){}
public MyDeserializer(Class<?> vc) {
super(vc);
}
public Set<String> deserialize(JsonParser p, DeserializationContext cxt) throws IOException, JsonProcessingException {
String input = p.readValueAs(String.class);
Set<String> output = new HashSet<>();
if(input != null) {
output.add(input);
}
return output;
}
}
I am getting an IllegalArgumentException referencing the "inputBaz" attribute, which I can provide details on. Does anyone see any obvious issue with my deserializer implementation? Thanks
You do not need to implement custom deserialiser, use ACCEPT_SINGLE_VALUE_AS_ARRAY feature. It works for sets as well:
Feature that determines whether it is acceptable to coerce non-array
(in JSON) values to work with Java collection (arrays,
java.util.Collection) types. If enabled, collection deserializers will
try to handle non-array values as if they had "implicit" surrounding
JSON array. This feature is meant to be used for
compatibility/interoperability reasons, to work with packages (such as
XML-to-JSON converters) that leave out JSON array in cases where there
is just a single element in array. Feature is disabled by default.
See also:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.util.ArrayList out of START_OBJECT token
Replace the 2 constructors with this no-arg constructor:
public MyDeserializer() {
super(TypeFactory.defaultInstance().constructCollectionType(Set.class, String.class));
}
ACCEPT_SINGLE_VALUE_AS_ARRAY as suggested is a good option.
Maybe your actual problem is more complicated but if not you could also try #JsonCreator instead of custom deserializer. Like:
public class Example {
#JsonCreator
public Example(#JsonProperty("inputFoo") String foo,
#JsonProperty("inputBar") String bar,
#JsonProperty("inputBaz") String strBaz) {
this.foo = foo;
this.bar = bar;
this.baz = new HashSet<>();
baz.add(strBaz);
}
private String foo;
private String bar;
private Set<String> baz;
}
Just to show that in more general case you might avoid implementing custom deserializer with #JsonCreator also but still make some simple conversions.
Lets say I have a group of classes A,B,C:
public class A:
int number;
public class B:
int number;
String address;
public class C:
int orderNumber;
How can i deserialize a Json string which contains only these classes, but in an unknown order (using Gson, in Java)? For example:
{//A
"number" : 3
}
//C
{
"orderNumber": 10
}
//B
{
"number" : 5
"address" : "New York"
}
//C
{
"orderNumber": 1
}
Thank you very much!
Answer by pirho is clean and easy if, like he said, your classes are simple as you've provided. But if that isn't the case, you can write your own deserializer.
public class PayloadJsonDeserializer implements JsonDeserializer {
#Override
public Object deserialize(JsonElement elm, Type type, JsonDeserializationContext context) throws JsonParseException {
// create java objects based on the properties in the json object
JsonPrimitive orderNumber = elm.getAsJsonObject().getAsJsonPrimitive("orderNumber");
if(!orderNumber.isJsonNull()) {
return new C(orderNumber.getAsInt());
}
return null;
}
}
Register your custom deserializer with Gson.
GsonBuilder gsonBuilder = new GsonBuilder()
.registerTypeAdapter(PayloadJson.class, new PayloadJsonDeserializer());
Gson gson = gsonBuilder.create();
Use it to deserialize your json.
gson.fromJson(jsonString, PayloadJson[].class);
This is not a generic or anyway a great strategy to do this in general if you have more complex classes with more fields.
But if the classes you want to deserialize are as simple as you provide as an example then create a class having all these fields
#Getter
public class Z {
private Integer orderNumber;
private Integer number;
private String address;
}
You will get a list of Zs and depending on which of the field are null or not null you can -if needed - later construct A, B or C from Z.
If classes to deserialize are more complex you anyway need to create some kind of a mechanism that determines what is the class to parse and to return. It could be like user1516873 suggested in the comment
Collection<Map<String,String>>
so for each item you would need to determine by what fields are present in that map to what class - A,B or C - item would be constructed.
I want to serialize a java map into json and I can definitely use either jackson or gson.
But when I serialize, I want to ignore specify key. Is it possible?
Map is . I don't have/want my Map backed by a POJO because the keys are very generic and could be anything. I understand if it was a POJO, we can use Ignore annotation to achieve.
You can create a custom class that extends HashMap:
public class MyMap extends HashMap<String, Object> {
}
and then register a custom serializer, like:
public class MyMapSerializer extends JsonSerializer<MyMap> {
#Override
public JsonElement serialize(MyMap arg0, Type arg1, JsonSerializationContext arg2) {
JsonObject result = new JsonObject();
for (String k : arg0.keySet()) {
if (whatever your condition is) {
result.add(k, arg0.get(k));
}
}
return result;
}
}
Then when you create the Gson object you need to initialize it by passing an instance of the serializer:
Gson gson = new GsonBuilder().registerTypeAdapter(MyMap.class, new MyMapSerializer()).create();
I have a relatively complex object which contains a number of fields. I need to serialize one of the fields using a custom serializer, but need to emulate the #JsonUnwrapped functionality.
For simplicity's sake I'll cut this down to two fields:
public class MyClass
{
#JsonProperty("subject")
private final String subject;
#JsonSerialize(using=MySenderSerializer.class)
private final MailActor sender;
}
and my custom serializer class is as follows:
public class MySenderSerializer extends StdSerializer<MailActor>
{
public MySenderSerializer()
{
super(MailActor.class, true);
}
#Override
public void serialize(final MailActor value, final JsonGenerator gen, final SerializerProvider provider) throws IOException
{
gen.writeStringField("from_name", value.getName());
gen.writeStringField("from_email", value.getAddress());
}
}
All of this is fine, except that the output JSON looks like this:
{
...
"subject": "test subject",
"sender": {
"from_name": "test from",
"from_email": "test#test.com"
},
...
}
and I need to unwrap the sender field so that the JSON looks like this:
{
...
"subject": "test subject",
"from_name": "test from",
"from_email": "test#test.com",
...
}
If I was using standard serializers I could use the #JsonUnwrapped annotation to do this, but it doesn't appear to be compatible with custom serializers. How can I obtain the required JSON output without writing a custom serializer for the MyClass object?
I have had to look for an alternative to #JsonUnwrapped too, as it was causing me some unrelated issues with this question.
The solution I have implemented would apply to your case similarly, using #JsonAnyGetter. In your MyClass ignore the attribute that needs the special serialization and instead add it with the JsonAnyGetter:
public class MyClass
{
#JsonProperty("subject")
private final String subject;
#JsonIgnore
private final MailActor sender;
#JsonAnyGetter
public Map<String, Object> serializeActor() {
return sender.serializeToMap();
// Of course, here you could create an empty map
// and add the properties of many different classes.
}
}
Then in the MailActor class you implement that method which will return a map with the properties that you may want.
public Map<String, Object> serializeToMap() {
final Map<String, Object> properties = new ArrayMap<>();
properties.put("from_name", this.getName());
properties.put("from_email", this.getAddress());
return properties;
}
The problem of going this way is deserializing the object, since the #JsonAnySetter doesn't get the map with the JSON in a map the same way you give it with the example above. In your case it's even more difficult, as your class' attributes are final. You'd need to use a #JsonCreator which would have to create all your attributes. Something along the lines of:
#JsonCreator
public MyClass( Map< String, String > json ) {
if (json.containsKey("subject")) {
subject = json.get("subject");
} else {
subject = "";
}
String name, address;
if (json.containsKey("from_name")) {
name = json.get("from_name");
} else {
name = "";
}
if (json.containsKey("from_email")) {
address = json.get("from_email");
} else {
address = "";
}
sender = new MailActor(name, address);
}
It's been a while since you posted the question, hopefully this is helpful in some way to someone coming to look for alternatives.
Well, that is because you have designed it to be that way, when jackson maps an object it will map the inner properties as sub-properties of that object, if you want it to serialize those two fields as if they were members of MyClass instead of MailActor, then declare them as such.
This may point out that your object design may have some small flaws.
I would write a custome serializer for the MyClass object but still, in the long run is not a viable solution.
This is an example of the kind JSON I'm trying to consume using GSON:
{
"person": {
"name": "Philip"
"father.name": "Yancy"
}
}
I was wondering if it were possible to deserialize this JSON into the following structure:
public class Person
{
private String name;
private Father father;
}
public class Father
{
private String name;
}
So that:
p.name == "Philip"
p.father.name == "Yancy"
Currently I am using #SerializedName to obtain property names containing a period, e.g.:
public class Person
{
private String name;
#SerializedName("father.name")
private String fathersName;
}
However, that's not ideal.
From looking at the documentation it doesn't appear to be immediately possible but there may be something I have missed - I'm new to using GSON.
Unfortunately I cannot change the JSON I'm consuming and I'm reluctant to switch to another JSON parsing library.
As far as I understand you can't do it in a direct way, because Gson will understand father.name as a single field.
You need to write your own Custom Deserializer. See Gson user's guide instructions here.
I've never tried it, but it doesn't seem to be too difficult. This post could be also helpful.
Taking a look at Gson's user guide and the code in that post, you'll need something like this:
private class PersonDeserializer implements JsonDeserializer<Person> {
#Override
public Person deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
JsonObject jobject = (JsonObject) json;
Father father = new Father(jobject.get("father.name").getAsString());
return new Person(jobject.get("name").getAsString(), father);
}
}
Assuming that you have suitable constructors...
And then:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Person.class, new PersonDeserializer());
Gson gson = gsonBuilder.create();
Person person = gson.fromJson(jsonString, Person.class);
And Gson will call your deserializer in order to deserialize the JSON into a Person object.
Note: I didn't try this code, but it should be like this or something very similar.
I couldn't do this with just Gson. I need a new library 'JsonPath'. I used Jackson's ObjectMapper to convert the object to string but you can easily use Gson for this.
public static String getProperty(Object obj, String prop) {
try {
return JsonPath.read(new ObjectMapper().writeValueAsString(obj), prop).toString();
} catch (JsonProcessingException|PathNotFoundException ex) {
return "";
}
}
// 2 dependencies needed:
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
// https://mvnrepository.com/artifact/com.jayway.jsonpath/json-path
// usage:
String motherName = getProperty(new Person(), "family.mother.name");
// The Jackson can be easily replaced with Gson:
new Gson().toJson(obj)