Deserialize flattened JSON keys to proper objects with GSON - java

We ingest an external API (we cannot change the JSON we receive) which produces JSON with flattened keys. For example:
{
"composite.key": "value",
"normal": "another value",
"composite.key2": "back here again..."
}
which we would ideally like to deserialize into:
public class SomeObject {
public String normal;
public Composite composite;
}
public class Composite {
public String key;
public String key2;
}
while we know we can write a custom deserializer, I would first like to check if there is support for this in GSON using annotations or by some other means.

You can use GSON's #SerializedName annotation on the Java fields.
Something like this
public class Composite {
#SerializedName("composite.key")
public String key;
#SerializedName("composite.key2")
public String key2;
}

I think the long and short of this is to just use Jackson which provides built-in support by annotating the wrapping object with #JsonUnwrapped(prefix="composite.").

Related

In Java, how can I map a string that is either JSON or XML to the same POJO, but with the XML having one different field/attribute name from the JSON?

I am currently using Jackson's XmlMapper and ObjectMapper. I want to map the string to a POJO (I think I'm using that term correctly) that has a private field with the same name as the JSON string's field. The XML string has a different name for the same field/attribute, and I want to use the JSON field name.
I also want to essentially "ignore" that field (while keeping it) and store it as something like a JsonNode, as the value of that field can be some complex, nested value without a known shape.
Example:
public static class OuterClass {
private String firstValue;
private InnerClass innerValue;
// ... getters/setters
}
public static class InnerClass {
private JsonNode data; // complex, nested, so no POJO to map to
private String otherValue;
// ... getters/setters
}
The JSON might look like this:
{
"innerValue": {
"data": {
... complex stuff
},
"otherValue": "more stuff"
},
"firstValue": "thingy"
}
The XML might look like this:
<result>
<innerValue>
<incorrectName>
... complex stuff
</incorrectName>
<otherValue>more stuff</otherValue>
</innerValue>
<firstValue>thingy</firstValue>
</result>
So the goal is to get the XML to work with that class, including both mapping incorrectName to the class' data, as well as storing the complex inner part as something like a JsonNode since I don't have a class to model it.
I have the JSON working with new ObjectMapper().readValue(jsonString, OuterClass.class), and I think the XML should work with new XmlMapper().readValue(xmlString, OuterClass.class), but I don't know where to go with annotations. I've looked at the different annotations available and I don't think I've found the right one. I've also read that I shouldn't convert XML to a JsonNode, as there can be problems with that. I don't need to convert it back to XML after, though, and can treat it as JSON once I receive the JSON/XML string. So, I'd appreciate some help, thanks!
#XmlAccessorType(XmlAccessType.PROPERTY)
public static class InnerClass {
private JsonNode data; // complex, nested, so no POJO to map to
private String otherValue;
// ... getters/setters
#XmlElement(name = "incorrectName")
protected JsonNode getData() {return data;}
}

Ignore enum case with JSON-B / Yasson

Using JSON-B / Yasson is there any way to ignore case of enums when deserializing?
public class MyObject{
MyEnum condition;
//getters and setters
}
public enum MyEnum{
NEW, OLD, REFURBISHED;
}
part of incoming JSON: "condition" : "new"
The problem is that the incoming JSON uses the enums in lowercase.
I don't thing this should be available out of the box. Because you technically can have both old and OLD as valid values of your enum living together, allowing for out-of-the-box uppercase conversion can break roundtrip equivalence. Think of serializing a MyEnum.old value to end up with a MyEnum.OLD value on deserialization.
You can however force such a behavior by using an adapter.
public static class MyAdapter implements JsonbAdapter<MyEnum, String> {
#Override
public String adaptToJson(MyEnum value) {
return value.name();
}
#Override
public MyEnum adaptFromJson(String s) {
return MyEnum.valueOf(s.toUpperCase());
}
}
Next, annotate the enum with #JsonbTypeAdapter.
#JsonbTypeAdapter(MyAdapter.class)
public enum MyEnum {
NEW,
OLD,
REFURBISHED;
}
Alternatively, you create your Jsonb provider as follows.
Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withAdapters(new MyAdapter()));

Problems while parsing enums using gson

I have a class like this:
#JsonInclude(Include.NON_NULL)
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class VPC
{
#NotNull()
private String id;
#NotNull()
#DynamoDBMarshalling(marshallerClass = SubnetTypeMarshaller.class)
private Map<SubnetType, List<String>> subnetTypeToId;
}
Here, SubnetType is a enum like this:
public enum SubnetType
{
AppSubnet,
DBSubnet,
DMZSubnet;
}
Now, I want to store the above in AWS DynamoDB. For this, I need to convert enum to a string and I have written the following.
public class SubnetTypeMarshaller implements DynamoDBMarshaller<Map<SubnetType, List<String>>>
{
private Gson gson = new GsonBuilder().create();
#Override
public String marshall(final Map<SubnetType, List<String>> securityGroupTypeListMap)
{
return gson.toJson(securityGroupTypeListMap);
}
#Override
public Map<SubnetType, List<String>> unmarshall(final Class<Map<SubnetType, List<String>>> aClass,
final String s)
{
return gson.fromJson(s, aClass);
}
}
But this doesn't work. While getting values from DB, I get the following error:
java.lang.String cannot be cast to java.lang.Enum (through reference chain: java.util.ArrayList[0]->["security_group_type_to_id"])
Am I missing something in this? I searched on other posts how to convert enums to string using #SerializedName annotation. But that didn't work either. I have also tried the solution mentioned in the other post, but it doesn't work. Maybe because my enum is in itself a part of map and I can't really annotate the enum attribute inside the map.
Gson provides default serialization for enums, if you want to change it you have to build your own adapter.
Check gson docs for registerTypeAdapter.

Create a POJO for a nested JSON for Retrofit

I am trying to create a POJO for the following JSON.
{
"key1":"value1",
"key2":"value2",
"hashmap":
{
"1":"val"
"2":"val"
...
"n":"val"
}
}
Now the POJO I tried was
public class MyPOJO{
#SerializedName{"key1"}
#Expose
private String key1;
#SerializedName{"key2"}
#Expose
private String key2;
#SerializedName{"hashmap"}
#Expose
private HashMap<String,String> hMap;
}
The problem is that the hashmap is not getting serialized. It is just ignored when the class gets serialized.
This is how I use the class in Retrofit.
#POST("/endpoint/")
void foo(#Body MyPojo, Callback<Response> callback);
One suggestion I came across is to use an inner class instead of the Hashmap. But my keys are dynamic, I cannot define variables for each key. I need to get the Hashmap serialized.
http://www.jsonschema2pojo.org/ use this website to generate pojo from json
Maybe you should have a look at retrofit converters and use a converter which supports maps.
Or use a custom converter.

Convert complex JSON to one-level POJO using Jackson

Let's imagine you have the following JSON:
{
"prop0": "value0",
"level1" : {
"prop1": "value1"
"prop2": "value2"
},
....
}
Can it be turned to simple Java object?
class Pojo {
private String prop0;
private String prop1;
private String prop2;
}
I don't want to create a intermediate class to wrap "level1".
What is come to my mind is to map my class in this way:
class Pojo {
private String prop0;
#JsonProperty("level1.prop1")
private String prop1;
#JsonProperty("level1.prop2")
private String prop2;
}
But unfortunately it doesn't work. The inverse problem - turn complex Java object to plain JSON can be simply solved using #JsonUnwrapped annotation.
Can you please suggest any workable solution for my issue?
You need to either write a custom deserializer, or add a setter that can transform the structure. For latter you could do something like
...
public void setLevel1(Map<String,String> values) { // or JsonNode
prop1 = values.get("prop1");
// and so forth; if names are regular, can use looping
}

Categories