DynamoDB: How can I create a table with nested JSON structure? - java

I want to create a table in dynamoDB with below structure.
{
"CartId": 123,
"UserId": 356,
"CartItems": [
{
"ProductId": 100,
"Quantity": 50
},
{
"ProductId": 121,
"Quantity": 51
}
]
}
Everywhere in tutorials and documents it says that we can only have below type of attributes in the table:
Set of Strings
Set of Numbers
Set of Binary
I can't think of a way to store above structure in DynamoDB. Could you please help out?
I am using object mapper Api of java. It would be great if you can also tell me how can I create a class which can be mapped to this particular table structure.

Simplest way is use #DynamoDBDocument
Add Maven dependency
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-dynamodb</artifactId>
<version>1.11.186</version>
</dependency>
Create POJO
#DynamoDBTable(tableName = "Customer")
public class Customer
{
#DynamoDBHashKey
#DynamoDBAutoGeneratedKey
private String id;
private String firstName;
private List<Foo> fooList;
}
#DynamoDBDocument
public static class Foo {
private String name;
}
Create a repository
#EnableScan
public interface CustomerRepository extends CrudRepository<Customer,String>
Then call customerRepository.save(customer). The result will be like this:
{
"firstName": "Test",
"fooList": [
{
"name": "foo"
},
{
"name": "foo2"
}
],
"id": "e57dd681-8608-4712-a39a-f3e0f31a5e27"
}

Bit Late to the party but would like to share this.
I was able to store a deep nested Json Doc by defining the column attribute as below in my Java Entity.
#DynamoDBAttribute
#DynamoDBTypeConvertedJson
private List<Map<String,Object>> pageData;
Input Json:
{
"userId": 359628,
"platform": "learn-web",
"inactive_duration": 0,
"total_time": 15,
"device_type": "web",
"page_data": [{
"page_details": {
"page_type": "segmentView",
"time": 15,
"segment_id": 65590,
"session_id": 13140,
"module_id": 4363
},
"items": [{
"type": "component",
"id": 267307,
"sub_type": "video",
"time": 10,
"metadata": {
"lastPlaybackRate": 1,
"currentTime": 0,
"isLocked": false,
"video": {
"videoType": "BRIGHTCOVE",
"viewingTime": 156,
"videoSize": 7120441,
"url": "5378655747001",
"URL": "5378655747001"
}
}
}]
}]
}
As stored in Dynamo DB-Snap

Posting to an old question because I did not think the solution was easy to find. Hope this helps someone.
Step 1: Create a complex Java object that mirrors the structure desired.
List<HashMap<String, Integer>> cartItems = new ArrayList<HashMap<String, Integer>>();
HashMap<String, Integer> item1 = new HashMap<String, Integer>();
item1.put("ProductId", 100);
item1.put("Quantity", 50);
cartItems.add(item1);
HashMap<String, Integer> item2 = new HashMap<String, Integer>();
item2.put("ProductId", 121);
item2.put("Quantity", 51);
cartItems.add(item2);
Step 2: Update DynamoDB item with the complex object.
I use a helper method:
private void updateAttribute(int id, String newAttribute, Object newValue){
Map<String, Object> newValues = new HashMap<String, Object>();
newValues.put(":value", newValue);
UpdateItemSpec updateItemSpec = new UpdateItemSpec()
.withPrimaryKey("id", id)
.withUpdateExpression("set " + newAttribute + " = :value")
.withValueMap(newValues);
siteTable.updateItem(updateItemSpec);
}
and then call thus:
updateAttribute(123, "CartItems", cartItems);
The newly added cart items attribute displays in DynamoDB like:
{
"CartItems": [
{
"ProductId": 100,
"Quantity": 50
},
{
"ProductId": 121,
"Quantity": 51
}
]
}
I have not tested an upsert scenario. In the past, upsert functionality did not seem to be present: https://forums.aws.amazon.com/thread.jspa?threadID=162907
Regarding reads of deeply nested items: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.AccessingItemAttributes.html#DocumentPaths

You can store JSONs in Dynamodb as Strings. It all depends on what you want to do with the data and how to retrieve it.
The DynamoDB Java API for instance, introduces Marshaller objects that transforms any Java object into a String so you can store it and fetch it automagically from a DynamoDB attribute.

If you have the JSON in a string, you can simply call
table.putItem(Item.fromJSON(jsonString));
The JSON needs to contain keys/values for the partition and sort key. For a conditional put, use
table.putItem(Item.fromJSON(jsonString), conditionString, nameMap, valueMap)

Not sure if these data types were available when this question was asked (there is a good chance that they were not) but these days you'd use the List datatype for the CartItems and each cart item would be of Map datatype.
Reference: DynamoDB Data Types

The following is the annotated way of doing it in V2 (Equivalent of #DynamoDBDocument). For more information check their GitHub Issue Page.
#DynamoDbBean
public class Cart {
private String cartId;
private String userId;
private List<CartItem> cartItems;
}
#DynamoDbBean
public class CartItem {
private String productId;
private Integer quantity;
}

Related

How to extract nested json values by using spring boot api call

I have the following json.
[
{
"id": 1,
"footwearList": [
{
"id": 1,
"name": "sandals",
"category": "men"
},
{
"id": 3,
"name": "sandals",
"category": "women"
}
],
"clothingList": [
{
"id": 1,
"name": "t-shirt",
"category": "men"
},
{
"id": 3,
"name": "tshirt",
"category": "women"
}
]
},
{
"id": 2,
"footwearList": [
{
"id": 2,
"name": "shoes",
"category": "men"
},
{
"id": 4,
"name": "shoes",
"category": "women"
}
],
"clothingList": [
{
"id": 2,
"name": "shirt",
"category": "men"
},
{
"id": 4,
"name": "shirt",
"category": "women"
}
]
}
]
Fetched this json from api call from controller and wanted to fetch nested values like (footwearlist, clothinglist) from the json through api call from controller.
And if found then again fetching by filtering category.
I tried using JsonPath with added dependency in pom.xml
Dependency:
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.7.0</version>
</dependency>
Tried to fetch the nested json but it didn,t work.
public List<Store> getCategory(){
List<Store> footwear = JsonPath.read(json, "$..footwear");
}
There's no need to introduce a new dependency, there are several possible way to solve this problem using Jackson.
Jackson Streaming API
If, for some reason, you don't want to materialize all data as objects and want to iterate over it. Then generate a JsonNode from your JSON using ObjectMapper.readTree(), and examine the field-names of each nested node. For that, you can use JsonNode.fields(). If the matching field was encountered - grab the data.
To check whether a field is matching you can make use of the regular expression. Here's regex-based predicate that would check if the given string contains substring "footwear":
public static final Predicate<String> FOOTWEAR = Pattern.compile("footwear").asPredicate();
That's how the code for iterating over the tree having the structure you shown might look like:
String json = // incoming JSON
ObjectMapper mapper = new ObjectMapper();
List<Item> items = StreamSupport.stream(mapper.readTree(json).spliterator(), false)
.<JsonNode>mapMulti((node, consumer) ->
node.fields().forEachRemaining(entry -> {
if (FOOTWEAR.test(entry.getKey())) entry.getValue().forEach(consumer);
})
)
.map(StreamingNestedObjects::nodeToItem)
.toList();
items.forEach(System.out::println);
Output:
Item{id=1, name='sandals', category='men'}
Item{id=3, name='sandals', category='women'}
Item{id=2, name='shoes', category='men'}
Item{id=4, name='shoes', category='women'}
Assuming that Item class look like this:
public class Item {
private int id;
private String name;
private String category;
// getters, setters, etc.
}
But the incoming JSON is not very massive, I would advise to make of the Jackson's Data binding.
Data Binding
Consider the following class Store:
public class Store {
private int id;
private Map<String, List<Item>> items = new HashMap<>();
#JsonAnySetter
public void readStore(String key, List<Item> value) {
items.put(key, value);
}
#JsonAnyGetter
public Map<String, List<Item>> writeStore(String key, List<Item> value) {
return items;
}
// no-args constructor, getter and setter for id property, etc.
}
One of its properties is a map of type Map<String, List<Item>> containing lists of items. This map can be serialized/deserialized via #JsonAnySetter and #JsonAnyGetter.
That how you can parse your incoming JSON into a list of Store instances:
ObjectMapper mapper = new ObjectMapper();
List<Store> stores = mapper.readValue(json, new TypeReference<>() {});
Then iterate over the list, check the Keys of the Maps and pick the stores you need.

Unable to parse json into list of custom Java objects

I am reading JSON from a file, and when trying to unmarshal the file to a java object, I am not getting expected array of custom Java object, however, getting array of LinkedHashMap
Please see below objects
public class Result<T>{
private final Map<String, T> data = new LinkedHashMap<>();
public Map<String, T> getAccounts(){
return accounts;
}
}
JSON ->
{
"data":{
"account":[
{
"accountDetails":{
"accountId":"123",
"accountType":"Decon"
}
},
{
"accountDetails":{
"accountId":"890",
"accountType":"ACX"
}
},
{
"accountDetails":{
"accountId":"123",
"accountType":"OOOP"
}
}
]
}
}
#Getter
#Setter
#ToString
public class Accounts{
#Getter
#Setter
public static class AcountDetails{
private String accountId;
private String accountType;
}
}
I am trying to read this Json as below
String accounts = Resource.asByteSource(Resources.getResource("account.json")).asCharSource(Charsets.UTF_8).read();
ObjectMapper mapper = new ObjectMapper();
Result<List<Accounts> finalResult = mapper.readValue(accounts, Result.class);
In finalResult variable ,
I am getting a map with key as "account" and value as list
But, instead of List of "Accounts" object, I am getting list of **LinkedHashMap**
So bascially after parsing, Instead of getting array of Accounts objects, I am getting array of LinkedHashMap
Please find attached screenshot. Please advise
If you are trying to get an Array of objects, your JSON would have to look something like this:
"element": [
{
"element1": "Value 1",
"element2": "Value 1"
},
{
"element1": "Value 1",
"element2": "Value 1"
}
]
When you are defining another object as you did:
"account":[
{
"accountDetails":{
"accountId":"123",
"accountType":"Decon"
}
},
You are creating a List of an Object that contains two more Objects. In Java that's a Map.
List<Map<AccountId, AccountType>
Hope it helps
Edit for formatting purposes if "Element1" were another Object:
"element": [
{
"element1": {
"element3": "Value",
"element4": "Value"
},
"element2": "Value 1"
},
{
"element1": "Value 1",
"element2": "Value 1"
}]
First of all can you confirm what you want to do here:
Map final Map<String, T> accounts = new LinkedHashMap<>();
inside class Result? Because if it is a variable, then remove the leading Map and don't make the variable as final.
Also you will need both setters and getters for the variable.
Secondly, your json has 'data' which has a account: [] pair, so first read value from data, then from account and you will get the list.
It worked after changing
Result<List<Accounts> finalResult = mapper.readValue(accounts, Result.class);
to
Result<List<Accounts>> finalResult = mapper.readValue(accounts, new TypeReference<Result<List<Accounts>>>() {});

Java GSON Json partial parsing

Say I have a JSON object representing an entity (can be any entity) like so:
{
"entity_id": "1",
"entity_name": "employee",
"entity_json": {
"employee_id": "e01",
"employee_name": "john",
"employee_phone_numbers": [
"1234567",
"8765433"
]
}
}
Note that entity_json can represent different entities having different structures as long as it is a valid JSON. For example, the following is another entity's representation:
{
"entity_id": "1",
"entity_name": "invoice",
"entity_json": {
"invoice_id": "1011",
"items": {
"item_id": "1",
"quantity": "3",
"price": "$100"
},
"date": "01-01-2020",
"customer": {
"id": "3",
"address": {
"street": "some_street",
"country": "CZ",
...
}
}
}
}
I want to be able to partially parse this JSON into an Entity POJO using Gson in Java. That is, I'll have an entity POJO like the one shown below:
public class Entity {
private String entity_id;
private String entity_name;
private String entity_json; // <-- entity_json is a String
// getters and setters
}
/*
* entity_json (for employee) = "{ \"employee_id\": \"1\", \"employee... }"
* entity_json (for invoice) = "{ \"invoice_id\": \"1011\", \"items... }"
*/
and I'm planning on performing any operation on entity_json using JsonPath.
Is there any way I can achieve this WITHOUT having to explicitly set entity_json in the JSON structure as a string with escapes?
Any help is appreciated here. Thanks!
You can avoid using a String for your entity_json by using Gson's JsonObject.
Here is my revised Entity class:
import com.google.gson.JsonObject;
public class MyEntity {
private String entity_id;
private String entity_name;
private JsonObject entity_json;
// getters and setters not shown
}
Then you can populate instances as follows:
MyEntity myEntityOne = new Gson().fromJson(JSON_ONE, MyEntity.class);
MyEntity myEntityTwo = new Gson().fromJson(JSON_TWO, MyEntity.class);
System.out.println(myEntityOne.getEntity_json());
System.out.println(myEntityTwo.getEntity_json());
In the above code, JSON_ONE and JSON_TWO are just strings containing the two sample JSONs from your question.
The console prints out the following (snipped for brevity):
{"employee_id":"e01","employee_name":"john","employee_phone_numbers":["1234567","8765433"]}
{"invoice_id":"1011","items":{"item_id":"1","quantity":"3","price":"$100"},"date":"01-01-2020"...
You can, of course, now use Gson to further manipulate each entity_json field as needed, since each one is itself a valid JSON object.

Java collections to JSON

My program has to output following JSON format:
{ "success": {
"code": 1,
"desc": "success" },
"response": {
"res1": [
{
"Item": "item1",
"Description": [
{
"desc": "ad1",
"active": true,
"details": [
{
"Type": "Type1",
"Count": 2,
"Status": true
},
{
"Type": "TYpe2",
"Count": 3,
"Status": false
},
]
},
{
"desc": "item2",
"active": true,
"details": [
{
"Type": "Type1",
"Count": 4,
"Active": true
}
]
},
{
"Item": "item2",
"Description": [
{
"desc": "d2",
"Active": true,
"details": [
{
"Type": "Type3",
"Count": 6,
"Active": true
},
]
},
]
}
]
}
}
I have wrote following to create this json format
HashMap<String, HashMap<String, String>> m1 = new HashMap<String, HashMap<String, String>>();
HashMap<String, String> m2 = new HashMap<String, String>();
HashMap<String, ArrayList<HashMap<String, String>>> m3 = new HashMap<String, ArrayList<HashMap<String, String>>>();
For creating the mentined JSON format, I tried to put m3 and m2 in m1. But since these are not with same type, it won't allow to put.
I am using GSON to convert the collection to JSON.
Gson gson = new Gson();
String json1 = gson.toJson(m1);
My Question is: For creating that JSON format, which collection in Java I have to use?
The best way to deal with a complex JSON structure like that is to create a class with the same structure with properties that represent the underlying arrays as lists. Then, you can use gson to convert the object(s) to JSON in a straightforward way.
In other words, instead of trying to fit the structure into a combination of complex collections or existing Java classes, create a class that is an exact representation of the data, populate it, and then convert it to JSON.
Why don't you let the code generated for you?
After the pojos are created you can of course change the code to tailor your needs.
Create a ResponseWrapper class and build your json accordingly.
Sample code:
public class ResponseWrapper<T>{
private Map<String, T> wrappedObjects = new HashMap<String, T>();
public ResponseWrapper() {
}
public ResponseWrapper(String name, T wrappedObject) {
this.wrappedObjects.put(name, wrappedObject);
}
..
..
Your json can be built like this:
ResponseWrapper response = new ResponseWrapper<ResponseWrapper>();
Items[] item = ....
//item can itself have Description, etc.
response.set("res1", (ArrayList) items);
Thanks for notepad++ and its json view plugin, i can read your json data in proper format. (although your data missing a few brackets)
In my oponion, json structure can be converted into Map but i personally don't like that idea much.
The other way to do is using Java POJO to define your JSON object and then convert it to json. Excellent example here
OK. We will check your data and create Java POJO for it. It's simple and should be something like this
public class YourJsonObj {
public SuccessObj success;
public ResponseObj response;
}
where SuccessObj and ResponseObj are another structure Classes like this:
public class SuccessObj {
public Integer code;
public String desc;
}
public class ResponseObj {
public List<ResObj> res1;
}
Another sub class appear: ResObj. All you have to do is continue define it:
public class ResObj {
public String Item; // << this property's name does't make java happy
public List<DescriptionObj> Description // << this property's name does't make java happy
}
Continue to do this definition till the end of your data. And you got it.
My suggestion is to replicate the same hierarchy of the fields in your JSON example.
By the Google Gson library you can convert (in 2 lines of code) your Java object to a JSON object. Please check the following example (from the user guide of the relative project).
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
// ==> json is {"value1":1,"value2":"abc"}

Dropwizard/Jersey returns Java List as JSON without root node

I am using dropwizard which uses jersey & jackson for json. My issue is when I return a list it does not specify a root.
I have a POJO class:
public class Company {
public String id;
public String name;
public String address;
}
and my resource is set up thus:
#GET
#Path("/companies/all")
public List<Company> getAllCompanies(){
...
return companies;
}
And I get the following response:
[{
"id": "01",
"name": "Yammer Corp",
"address": "1 Finite Loop"
},
{
"id": "02",
"name": "DropWizards Inc",
"address": "4 Magic Square, Olympus"
}]
While what I want is something like below:
{"Companies" :
[{
"id": "01",
"name": "Yammer Corp",
"address": "1 Finite Loop"
},
{
"id": "02",
"name": "DropWizards Inc",
"address": "4 Magic Square, Olympus"
}
]}
Any ideas? Thanks in advance.
You need to create one more POJO wrapping the List<Company>
public class ApiResponse
{
private List<Company> Companies;
//Getter and Setter
//Public Constructor
}
The Change required in your GET Method is:
#GET
#Path("/companies/all")
public ApiResponse getAllCompanies(){
//Set your ApiResponse Object with the companies List.
ApiResponse apiResponse = new ApiResponse(companies);
return apiResponse;
}
I believe you can customize using Jackson APIs. Here is one approach which allows you to set the root of the generated JSON using ObjectWriter.
#GET
#Path("/companies/all")
public Response getAllCompanies() throws JsonProcessingException {
List<Company> companies = Lists.newArrayList();
Company yc = new Company();
yc.id = "01";
yc.name = "Yammer Corp";
yc.address = "1 Finite Loop";
companies.add(yc);
Company dw = new Company();
dw.id = "02";
dw.name = "DrowWizards Inc";
dw.address = "4 Magic Square, Olympus";
companies.add(dw);
ObjectMapper objectMapper = new ObjectMapper();
ObjectWriter writer = objectMapper.writer();
String entity = writer.withRootName("Companies").writeValueAsString(companies);
return Response.ok(entity).build();
}
If you look at your code you are asking to return a list of companies:
public List<Company> getAllCompanies(){
If you want to return a JSON object with a Companies value then you need a suitable Java object which matches this.
public class MyListOfCompanies {
List<Companies> companies;
}
And then you would ask your code to return that instead:
public MyListOfCompanies getAllCompanies(){
However, do consider if you really want to do this. If you think about the situation of someone coding to your API, would they rather receive a list of companies (as they asked for and the API implies they will obtain), or an object that serves no purpose other than to contain the list of companies?
Finally, for good REST design the common way to obtain a list of all companies is just to use the path /companies, not /companies/all.

Categories