DynamoDB nested map to Java Object - java

I am using a DynamoDB-table with DynamoDBAttributeType.M maps nested in one another. The field of my table I'm having problems with looks like this:
"Data": {
"EnglishName": "Balcony",
"High": {
"Status": true,
"Triggered": true,
"Value": 5
},
"Low": {
"Status": true,
"Triggered": false,
"Value": 1
},
"TagName": "tag1"
}
all the keys of the map are Strings and some of the values are Strings and some are maps. In my Java code that data is represented by a class, which is an attribute of another class, which represents whole table. Attributes 'Low' and 'High' are also represented by Java class and attributes of data class.
I have tried to map it to Java object using many ways, mostly by DynamoDBTypeConverter. Hardest part is that I can't find any information about this with Google. I only found one example of converter class where attribute type is S not M.
I keep on getting error:
could not unconvert attribute
How this can be done?

Finally I have a functioning solution. Basically the answer is to use Map<String, Attributevalue> and desired object type in DynamoDBTypeConverter like this:
public class DataConverter implements DynamoDBTypeConverter<Map<String, AttributeValue>, Data> {
#Override
public Map<String, AttributeValue> convert(Data data) {
ObjectMapper mapper = new ObjectMapper();
Item item = new Item()
.withString("englishName", data.getEnglishName())
.withMap("high", mapper.convertValue(data.getHigh(), Map.class))
.withMap("low", mapper.convertValue(data.getLow(), Map.class))
.withString("tagName", data.getTagName());
return ItemUtils.toAttributeValues(item);
}
#Override
public Data unconvert(Map<String, AttributeValue> data) {
Data tagData = new Data();
try {
ObjectMapper mapper = new ObjectMapper();
String item = ItemUtils.toItem(data).toJSON();
JsonNode json = mapper.readTree(item);
tagData = mapper.convertValue(json, Data.class);
} catch (JsonProcessingException ex) {
Logger.getLogger(TagDataConverter.class.getName()).log(Level.SEVERE, null, ex);
}
return tagData;
}
I also needed to write type converters to attributes that are objects and annotate them in class:
public class Data {
private String tagName;
private String englishName;
#DynamoDBTypeConverted(converter = AlertConverter.class)
private Alert low;
#DynamoDBTypeConverted(converter = AlertConverter.class)
private Alert high;
The AlertConverter.class is an ordinary DynamoDBTypeConverter that takes in <String, Alert>.

Have you tried using a tool like Gson? If you're getting back the DynamoDB response as a JSON string, try this:
Gson gson = new Gson();
Data data = gson.fromJson(jsonString, Data.class);

Related

Deserialize JSON with repeating keys to a list inside Java Object

If an "action" key-value pair is repeated, I want to append each associated "myObject" to a list as shown below. Is there a way to achieve this using GSON or JACKSON? Unfortunately, there is no option to edit the input JSON. If the ask is not clear, please let me know.
Input
[
{
myObject: {
name: "foo",
description: "bar"
},
action: "create",
},
{
myObject: {
name: "baz",
description: "qux"
},
action: "create",
},
];
Required Output
{
"action": "create",
"myObject": [
{
name: "foo",
description: "bar"
},
{
name: "baz",
description: "qux"
},
]
};
I am new to JSON parsing in Java and unfortunately haven't found a use case like mine on StackOverflow. I have tried configuring my ObjectMapper like so -
new ObjectMapper().configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
and using
#JsonAnySetter
annotation, but haven't gotten them to work yet.
You could solve this with two separate model classes, one for the original structure and one for the transformed one. For simplicity I call them OriginalModel and TransformedModel below, you should probably pick more meaningful names. The following code uses Gson but you can probably achieve something similar with Jackson as well.
class OriginalModel {
String action;
MyObjectData myObject;
}
class TransformedModel {
String action;
List<MyObjectData> myObject;
public TransformedModel(String action, List<MyObjectData> myObject) {
this.action = action;
this.myObject = myObject;
}
}
class MyObjectData {
String name;
String description;
}
If you declare these classes as nested classes you should make them static.
Then you can first parse the JSON data with the original model class, manually create the desired result structure using the transformed class and serialize that to JSON:
Gson gson = new Gson();
List<OriginalModel> originalData = gson.fromJson(json, new TypeToken<List<OriginalModel>>() {});
// Group MyObjectData objects by action name
// Uses LinkedHashMap to preserve order
Map<String, List<MyObjectData>> actionsMap = new LinkedHashMap<>();
for (OriginalModel model : originalData) {
actionsMap.computeIfAbsent(model.action, k -> new ArrayList<>())
.add(model.myObject);
}
List<TransformedModel> transformedData = new ArrayList<>();
for (Map.Entry<String, List<MyObjectData>> entry : actionsMap.entrySet()) {
transformedData.add(new TransformedModel(entry.getKey(), entry.getValue()));
}
String transformedJson = gson.toJson(transformedData);

How to convert JSON fields into a JAVA map using GSON

I have a JSON data which looks like this:
{
"status": "status",
"date": "01/10/2019",
"time": "10:30 AM",
"labels": {
"field1": "value1",
"field2": "value2",
...
"field100": "value100"
}
"description": "some description"
}
In my Java code, I have two classes:
Alerts class which has the following fields - status, date, time, description and Labels class.
The inner Labels class which is supposed to hold all the fields from field1 through field100 (and more)
I'm parsing this JSON into GSON like this:
Alerts myAlert = gson.fromJson(alertJSON, Alert.class);
The above code parses the JSON into the Alert object and the Labels object.
Question:
Instead of mapping the fields (field1, field2, etc) inside Labels object as individual String fields, how can I parse them into a map?
For example, the Labels object would look like this:
public class Labels {
// I want to parse all the fields (field1, field2, etc) into
// this map
Map<String, String> fields = new HashMap<>();
}
How do I do this?
Declaring Alert object like this:
public class Alert {
private String description;
private String status;
private Map<String, String> labels;
...
}
works for me and this code
Alert myAlert = gson.fromJson(alertJSON, Alert.class);
System.out.println(myAlert.getLabels());
prints the map as {field1=value1, field2=value2, field100=value100}
So that no intermediate object is required
You can use TypeToken to directly specify labels.
import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;
Type mapType = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> myMap = gson.fromJson("{'field1':'value1','field2':'value2'}", mapType);
For general cases - some more flexible way: gson can register type adapters:
Gson gson = new GsonBuilder().registerTypeAdapter(Labels.class, new LabelsDeserializer()).create();
And deserializer for your case is:
public class LabelsDeserializer implements JsonDeserializer<Labels>
{
#Override
public Labels deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException
{
if (!jsonElement.isJsonNull())
{
Labels label = new Labels();
jsonElement.getAsJsonObject().entrySet().forEach(entry -> label.getFields().put(entry.getKey(), entry.getValue().getAsString()));
return label;
}
else
{
return null;
}
}
}
For serlializing it's needed to implement JsonSerializer<...>

Parse JSON object containing multiple nested objects without making class for each nested object

I'm using GSON in Android to parse a JSON object, part of which contains multiple nested objects holding all of the same fields. For example, the JSON structure looks similar to this:
{
"name": "nestedJSONExample",
"divisions": {
"division1": {
"id": string
"name": string,
"alsoKnownAs": [
string
],
}
"division2": {
"id": string
"name": string,
"alsoKnownAs": [
string
],
}
...
"division99" {
"id": string
"name": string,
"alsoKnownAs": [
string
],
}
}
}
In this example all of the "division##" nested objects contain all of the same fields, is there a way to parse this JSON into a Java class without creating model classes for each "division##" object?
i.e. can I create a Java structure like:
divisions.division##.id
without having to make classes for each individual division?
You seem to have a little confusion: you don't need a mapping class for each division## node since you can reuse one class multiple times regardless the property names. You might need from zero to two custom mapping classes regarding the way you prefer:
0 custom mapping classes if traversing a parsed JSON object on your own;
1 custom mapping class if applying advanced parsing techniques and combining the mapping with type adapters or JSON objects;
2 custom mapping classes for exact mapping.
The examples below are written with Java 8 language features and Java 8 Stream API but can be re-written with Java 6 easily. The JSON constant below is just a String with the following JSON document:
{
"name": "nestedJSONExample",
"divisions": {
"division1": {"id": "id1", "name": "name1", "alsoKnownAs": ["alsoKnownAs1A"]},
"division2": {"id": "id2", "name": "name2", "alsoKnownAs": ["alsoKnownAs2A"]},
"division3": {"id": "id3", "name": "name3", "alsoKnownAs": ["alsoKnownAs3A"]},
"division4": {"id": "id4", "name": "name4", "alsoKnownAs": ["alsoKnownAs4A"]},
"division5": {"id": "id5", "name": "name5", "alsoKnownAs": ["alsoKnownAs5A"]},
"division6": {"id": "id6", "name": "name6", "alsoKnownAs": ["alsoKnownAs6A"]}
}
}
No mappings
JsonElement is a built-in Gson class representing any JSON element. Combining JsonElement class and its child classes elements, Gson can build a JSON tree that reflects a given JSON document structure. So just traversing from the root is enough.
final Gson gson = new Gson();
final List<String> ids = gson.fromJson(JSON, JsonElement.class)
.getAsJsonObject()
.get("divisions") // get the divisions property
.getAsJsonObject()
.entrySet() // and traverse its key/value pairs
.stream()
.map(Entry::getValue) // discarding the keys
.map(JsonElement::getAsJsonObject)
.map(jo -> jo.get("id")) // take the id property from the every `division` object
.map(JsonElement::getAsJsonPrimitive)
.map(JsonPrimitive::getAsString)
.collect(toList());
System.out.println(ids);
Exact mappings
Here you could need just two mapping classes to describe the relations between JSON objects. The divisions node can be just a Map holding arbitrary keys and Division values.
final class OuterWithMap {
//String name;
Map<String, Division> divisions;
}
final class Division {
String id;
//String name;
//List<String> alsoKnownAs;
}
final Gson gson = new Gson();
final List<String> ids = gson.fromJson(JSON, OuterWithMap.class)
.divisions
.values() // use map values only ignoring the keys
.stream()
.map(d -> d.id)
.collect(toList());
System.out.println(ids);
Not exact mappings
This is the most complicated one and shows advanced techniques in parsing JSON with Gson and mapping given JSON documents to mapping classes may not reflect the real structure therefore making transformations on-fly.
final class OuterWithList {
//String name;
#JsonAdapter(NoKeysTypeAdapterFactory.class)
List<Division> divisions;
}
final class NoKeysTypeAdapterFactory
implements TypeAdapterFactory {
// No accessible constructor needed - Gson can instantiate it itself
private NoKeysTypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Is it a list?
if ( List.class.isAssignableFrom(typeToken.getRawType()) ) {
// Try to determine the list element type
final Type elementType = getElementType(typeToken.getType());
// And create a custom type adapter instance bound to the specific list type
#SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) getNoKeysTypeAdapter(gson, elementType);
return typeAdapter;
}
// Otherwise just tell Gson try to find another appropriate parser
return null;
}
private static Type getElementType(final Type type) {
// Is it a generic type with type parameters?
if ( type instanceof ParameterizedType ) {
final ParameterizedType parameterizedType = (ParameterizedType) type;
// If yes, then just take the first type argument since java.util.List can only one type
return parameterizedType.getActualTypeArguments()[0];
}
// Otherwise java.lang.Object due to either Java generics type erasure or raw types usage
return Object.class;
}
}
final class NoKeysTypeAdapter<E>
extends TypeAdapter<List<E>> {
private final Gson gson;
private final Type elementType;
private NoKeysTypeAdapter(final Gson gson, final Type elementType) {
this.gson = gson;
this.elementType = elementType;
}
static <E> TypeAdapter<List<E>> getNoKeysTypeAdapter(final Gson gson, final Type elementType) {
return new NoKeysTypeAdapter<>(gson, elementType);
}
#Override
public void write(final JsonWriter out, final List<E> value) {
throw new UnsupportedOperationException();
}
#Override
public List<E> read(final JsonReader in)
throws IOException {
final List<E> list = new ArrayList<>();
// Make sure that the next JSON stream token is `{`
in.beginObject();
// Read until the object ends
while ( in.peek() != END_OBJECT ) {
// Ignore the found JSON object property name
in.nextName();
// And delegate the property value parsing to a downstream parser
final E element = gson.fromJson(in, elementType);
list.add(element);
}
// Make sure that the JSON stream is finished with the `}` token
in.endObject();
return list;
}
}
Using a special querying library
There are some libraries like JsonPath that can make querying JSON documents somewhat easier. JsonPath can work without Gson, however, as far as I understand, it uses another JSON parsing library, and does not parse JSON itself (but I don't know how it actually is). Example of use:
final JsonPath jsonPath = JsonPath.compile("$.divisions.*.id");
final List<String> ids = jsonPath.<JSONArray>read(JSON)
.stream()
.map(o -> (String) o)
.collect(toList());
System.out.println(ids);
All four examples above have the following output:
[id1, id2, id3, id4, id5, id6]
Using GSON your best bet is to write a custom Deserializer (example) or a TypeAdapter (example), this will allow you to do whatever you want with the structure then return a single (top level) object

How to use dynamic json value on my POJO class with Gson?

{
"localeCode": "",
"map": {
"DynamicName1": [],
"DynamicName2": [
{
"date": "2016-05-15T00:00:00",
"seqId": 1,
"status": 10
},
{
"date": "2016-05-16T00:00:00",
"seqId": 83,
"status": 10
}
],
"DynamicName3": [],
"DynamicName4": []
},
"respCode": 100,
"respMsg": "success",
"status": 1
}
How to correctly map this kind of json. If you can see that, Dynamic is a dynamic name. So far I have done this :
public class MapModel {
public MapObject map;
public static class MapObject{
public java.util.Map<String, Student> queryStudent;
public static class Student{
public String date;
public String seqId;
public String status;
}
}
}
But when run the app. I'm getting NullPointerException. Can somebody help me?
You're getting the NullPointerException accessing queryStudent of your MapObject inside your MapModel since it's not correctly filled when you're trying to deserialize your Json.
So to solve your problem look at Gson documentation where you can see:
You can serialize the collection with Gson without doing anything
specific: toJson(collection) would write out the desired output.
However, deserialization with fromJson(json, Collection.class) will
not work since Gson has no way of knowing how to map the input to the
types. Gson requires that you provide a genericised version of
collection type in fromJson(). So, you have three options:
Use Gson's parser API (low-level streaming parser or the DOM parser
JsonParser) to parse the array elements and then use Gson.fromJson()
on each of the array elements.This is the preferred approach. Here is
an example that demonstrates how to do this.
Register a type adapter for Collection.class that looks at each of the
array members and maps them to appropriate objects. The disadvantage
of this approach is that it will screw up deserialization of other
collection types in Gson.
Register a type adapter for MyCollectionMemberType and use fromJson()
with Collection.
Since your MapObject containts a java.util.Map but your class itself it's not generic, I think that a good approach for your case is create a Deserializer.
Before this try to clean up your class definition, to provide constructors to make the deserializer easy to build. Your POJO classes could be:
Student class
public class Student{
public String date;
public String seqId;
public String status;
public Student(String date, String seqId, String status){
this.date = date;
this.seqId = seqId;
this.status = status;
}
}
MapObject class
Note: I change you Map definition, since in your Json seems that could be multiple students for each DynamicName (look at DynamicName2 from your question), so I use Map<String,List<Student>> instead of Map<String,Student>:
public class MapObject{
public Map<String,List<Student>> queryStudent;
public MapObject(Map<String,List<Student>> value){
this.queryStudent = value;
}
}
MapModel class
public class MapModel {
public MapObject map;
}
Now create a Deserializer for your MapObject:
public class MapObjectDeserializer implements JsonDeserializer<MapObject> {
public MapObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
Map<String,List<Student>> queryStudents = new HashMap<String,List<Student>>();
// for each DynamicElement...
for (Map.Entry<String,JsonElement> entry : json.getAsJsonObject().entrySet()) {
List<Student> students = new ArrayList<Student>();
// each dynamicElement has an Array so convert and add an student
// for each array entry
for(JsonElement elem : entry.getValue().getAsJsonArray()){
students.add(new Gson().fromJson(elem,Student.class));
}
// put the dinamic name and student on the map
queryStudents.put(entry.getKey(),students);
}
// finally create the mapObject
return new MapObject(queryStudents);
}
}
Finally register the Deserializer and parse your Json:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MapObject.class, new MapObjectDeserializer());
Gson gson = builder.create();
MapModel object = gson.fromJson(YourJson,MapModel.class);
DISCLAIMER: For fast prototyping I test this using groovy, I try to keep the Java syntax but I can forget something, anyway I think that this can put you on the right direction.
Hope it helps,

In Gson, how can I deserialize a Map of Lists of arbitrary classes?

I have an API that returns data as follows:
{
gene: [ ],
attribute: [ ],
dataset: [ ]
}
Each List contains an object that should be deserialized to a specific class. For example, each JSON object in the dataset list should be deserialized to a Dataset class. I have not been able to get this to work. Here is my current attempt:
Type listType = new TypeToken<HashMap<String, List<Object>>>() {}.getType();
HashMap<String, List<Object>> map = new Gson().fromJson(json, listType);
List<Dataset> datasets = (List<Dataset>) (Object) map.get("dataset");
Dataset ds = datasets.get(0);
The error I am getting is:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to Dataset
Thanks in advance.
A colleague suggested writing a wrapper class to describe the schema. This has made serializing and deserializing straightforward.
public class JsonSchema {
private List<Dataset> datasets;
...
public List<Dataset> getDatasets() {
return datasets;
}
public void setDatasets(List<Dataset> datasets) {
this.datasets = datasets;
}
...
}
Then, to serialize the JSON, I use the setters and to deserialize the JSON, I write:
JsonSchema jsonSchema = new Gson().fromJson(json, JsonSchema.class);
List<Dataset> datasets = jsonSchema.getDatasets();

Categories