Related
I have a Cosmos DB and want to write different kind of documents to it. The structure of the documents is dynamic and can change.
I tried the following. Let's say I have the following class:
class CosmosDbItem implements Serializable {
private final String _id;
private final String _payload;
public CosmosDbItem(String id, String payload) {
_id = id;
_payload = payload;
}
public String getId() {
return _id;
}
public String getPayload() {
return _payload;
}
}
I can create then the document with some JSON as follows:
CosmosContainer _container = ...
CosmosDbItem dataToWrite = new CosmosDbItem("what-ever-id-18357", "{\"name\":\"Jane Doe\", \"age\":42}")
item = _cosmosContainer.createItem(dataToWrite, partitionKey, cosmosItemRequestOptions);
This results in a document like that:
{
"id": "what-ever-id-18357",
"payload": "{\"name\":\"Jane Doe\", \"age\":42}",
"_rid": "aaaaaaDaaAAAAAAAAAA==",
"_self": "dbs/aaaaAA==/colls/aaaaAaaaDI=/docs/aaaaapaaaaaAAAAAAAAAA==/",
"_etag": "\"6e00c443-0000-0700-0000-5f8499a70000\"",
"_attachments": "attachments/",
"_ts": 1602525607
}
Is there a way in generating the payload as real JSON object in that document? What do I need to change in my CosmosDbItem class? Like this:
{
"id": "what-ever-id-18357",
"payload": {
"name":"Jane Doe",
"age":42
},
"_rid": "aaaaaaDaaAAAAAAAAAA==",
"_self": "dbs/aaaaAA==/colls/aaaaAaaaDI=/docs/aaaaapaaaaaAAAAAAAAAA==/",
"_etag": "\"6e00c443-0000-0700-0000-5f8499a70000\"",
"_attachments": "attachments/",
"_ts": 1602525607
}
Here is my solution that I ended up. Actually it is pretty simple once I got behind it. Instead of using CosmosDbItem I use a simple HashMap<String, Object>.
public void writeData() {
...
Map<String, Object> stringObjectMap = buildDocumentMap("the-id-", "{\"key\":\"vale\"}");
_cosmosContainer.createItem(stringObjectMap, partitionKey, cosmosItemRequestOptions);
...
}
public Map<String, Object> buildDocumentMap(String id, String jsonToUse) {
JSONObject jsonObject = new JSONObject(jsonToUse);
jsonObject.put("id", id);
return jsonObject.toMap();
}
This can produce the following document:
{
"key": "value",
"id": "the-id-",
"_rid": "eaaaaaaaaaaaAAAAAAAAAA==",
"_self": "dbs/eaaaAA==/colls/eaaaaaaaaaM=/docs/eaaaaaaaaaaaaaAAAAAAAA==/",
"_etag": "\"3b0063ea-0000-0700-0000-5f804b3d0000\"",
"_attachments": "attachments/",
"_ts": 1602243389
}
One remark: it is important to set the id key in the HashMap. Otherwise one will get the error
"The input content is invalid because the required properties - 'id; ' - are missing"
I try to map dynamic fields of a document in mongodb collection to a model in spring boot project,
well this is the document from mongodb
Example of MongoDB Document
{
"_id":"5f78e5599fe104f27eb61333",
"conducteur":{
"cin":"F21458",
"nomComplet":"Ahmed Ayoub",
"idConducteur":"4"
},
"vehicule":{
"matricule":"4585|45| a",
"engin":"Benne",
"idVehicule":"1"
},
"date":"21-11-2019 22:07:44",
"site":"Paris",
"rating":37.04,
"etat":true,
"imageUrl":null,
"motif":"nothing",
"controlleur":{"cin":"F44585","nomComplet":"Test","idController":"2"},
"checklistConducteur":[false,true,false,true,true],
"checklistEquipement":[false,false,false,true,true],
"checklistEngin":[false,true,true,false,true,false,true],
"checklistAttelage":[true,true,true,false,true]
}
Then I create the model with the standards or fixed fields and one field to handle dynamic properties, the model seems like
#Document(collection = "checkList")
public class CheckList {
private String id;
private Map<String, String> conducteur;
private Map<String, String> vehicule;
private Date date;
private String site;
private double rating;
private boolean etat;
private String imageUrl;
private String motif;
private Map<String, String> controlleur;
private Map<String, Boolean[]> catchAll = new HashMap<>();
public Map<String, Boolean[]> getCatchAll() {
return catchAll;
}
#JsonAnySetter
public void setCatchAll(Map<String, Boolean[]> catchAll) {
this.catchAll = catchAll;
}
}
So what i want is to combine or map checklistConducteur, checklistEquipement, checklistEngin and checklistAttelage from mongodb document to the field catchAll, or to get nested inside it.
I tried to use the annotations like #JsonAnyGetter, #JsonAnySetter, or to create a class CatchAll have these properties, but give me the same result:
API Response:
{
"id": "5f78e5599fe104f27eb61333",
"conducteur": {
"cin": "F21458",
"nomComplet": "Ahmed Ayoub",
"idConducteur": "4"
},
"vehicule": {
"matricule": "4585|45| a",
"engin": "Benne",
"idVehicule": "1"
},
"date": "21-11-2019 22:07:44",
"site": "Paris",
"rating": 37.04,
"etat": true,
"imageUrl": null,
"motif": "nothing",
"controlleur": {
"cin": "F44585",
"nomComplet": "Test",
"idController": "2"
},
"catchAll": {}
}
what i want as result from API:
"catchAll": {
"checklistConducteur":[false,true,false,true,true],
"checklistEquipement":[false,false,false,true,true],
"checklistEngin":[false,true,true,false,true,false,true],
"checklistAttelage":[true,true,true,false,true]
}
If anyone has a solution or suggestion please help me, I will be thankful.
My API needs to return a list of entry objects from the JSON below. I am using jersey and jackson. I would ideally like to only create a java class PermissionEnty , and my API to return a list of PermissionEntry objects from the JSON. I am not able to deserialize using the below approach? Can someone advise what could be the issue? I have added UNWRAP_ROOT_VALUE so I presume the 'list' node it ignored, and I would get items below 'list' node.
public class PermissionEntry {
private String id;
private String displayName;
private String memberType;
}
and the json;
{
"list": {
"pagination": {
"count": 5,
"hasMoreItems": false,
},
"entries": [
{
"entry": {
"displayName": "norma",
"id": "norma",
"memberType": "PERSON"
}
},
{
"entry": {
"displayName": "clara",
"id": "clara",
"memberType": "PERSON"
}
},
{
"entry": {
"displayName": "michael",
"id": "mich",
"memberType": "PERSON"
}
}
]
}
}
PermissionEntries
public class PermissionEntries {
#JsonProperty(value = "entries")
#JsonDeserialize(using = PermissionEntryDeserializer.class)
private List<PermissionEntry> entries;
public List<PermissionEntry> getEntries() {
return entries;
}
public void setEntries(List<PermissionEntry> entries) {
this.entries = entries;
}
}
Below is the deserializer that I am using
public class PermissionEntryDeserializer extends JsonDeserializer<List<PermissionEntry>> {
private static final String ENTRY = "entries";
private static final ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
private static final CollectionType collectionType =
TypeFactory
.defaultInstance()
.constructCollectionType(List.class, PermissionEntry.class);
#Override
public List<PermissionEntry> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
ObjectNode objectNode = mapper.readTree(jsonParser);
JsonNode nodeEntries = objectNode.get(ENTRY);
if (null == nodeEntries // if no ENTRY node could be found
|| !nodeEntries.isArray() // or ENTRY node is not an array
|| !nodeEntries.elements().hasNext()) // or ENTRY node doesn't contain any entry
return null;
return mapper.readerFor(collectionType).readValue(nodeEntries);
}
}
Service API
public Optional<List<PermissionEntry>> getPermissionsForGroup(String groupName) {
Response response = getTarget()
.path("/api/group/" + groupName + "/members")
.request()
.get();
PermissionEntries list = response.readEntity(PermissionEntries.class);
}
I don't understand what you mean in this question 'Can someone please tell me how many java classes do I need to create to get a list of entries.' but you had already an entry object called PermissionEntry. You will have the list of this object.
This is the jersey client of your data with jakcson .
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
Client client = Client.create(clientConfig);
String URL = "http://{{host}}:{{port}}/entry";
WebResource webResourceGet = client.resource(URL);
ClientResponse response = webResourceGet.accept("application/json").get(ClientResponse.class);
String output = response.getEntity(String.class);
ResponseList responseList= mapper.readValue(output , ResponseList .class);//here is the permissionEntry list that you wil have
Also, you should create an object given name as Pagination for pagination that is in the json data. You can make an another object that includes List<PermissionEntry> and Pagination called ResponseList.
I just need a quick advice, as i am a total beginner with JSON.
I get the following response from a webserver, which i store in a String:
{
"station62":[
{
"departureTime":1982,
"delay":"-1.0",
"line":"6",
"stationName":"randomname",
"direction":2
}
],
"station63":[
{
"departureTime":1234,
"delay":"-1.0",
"line":"87",
"stationName":"anotherrandomname",
"direction":2
}
],
"station64":[
{
"departureTime":4542,
"delay":"-1.0",
"line":"4",
"stationName":"yetanotherrandomname",
"direction":2
}
],
"station65":[
{
"departureTime":1232,
"delay":"-1.0",
"line":"23",
"stationName":"onemorerandomname",
"direction":2
}
]
}
(Sorry, i dont know how the indent works on here.)
The response is longer, but for this example it is shortened. So what i need is to parse the information of each of these "station"-objects.
I dont need the "station62"-String, i only need "departureTime", "delay", "line", "stationName" and "direction" in a java-object.
I have read this, but i couldnt make it work: https://stackoverflow.com/a/16378782
I am a total beginner, so any help would be really appreciated.
Edit: Here is my code:
I made a wrapper class just like in the example link above. I played with the map types a bit, but no luck so far.
public class ServerResponse
{
private Map<String, ArrayList<Station>> stationsInResponse = new HashMap<String, ArrayList<Station>>();
public Map<String, ArrayList<Station>> getStationsInResponse()
{
return stationsInResponse;
}
public void setStationsInResponse(Map<String, ArrayList<Station>> stationsInResponse)
{
this.stationsInResponse = stationsInResponse;
}
}
The problem is, that this map does not get filled by the gson.fromJSON(...)-call i am showing below. The map size is always zero.
Station class looks like this:
public class Station
{
String line;
String stationName;
String departureTime;
String direction;
String delay;
// getters and setters are there aswell
}
And what i am trying to do is
Gson gson = new Gson();
ServerResponse response = gson.fromJson(jsonString, ServerResponse.class);
where "jsonString" contains the JSON response as a string.
I hope that code shows what i need to do, it should be pretty simple but i am just not good enough in JSON.
EDIT 2
Would i need my JSON to be like this?
{"stationsInResponse": {
"station62": [{
"departureTime": 1922,
"delay": "-1.0",
"line": "8",
"stationName": "whateverrandomname",
"direction": 2
}],
"station67": [{
"departureTime": 1573,
"delay": "-1.0",
"line": "8",
"stationName": "rndmname",
"direction": 2
}],
"station157": [{
"departureTime": 1842,
"delay": "-2.0",
"line": "8",
"stationName": "randomname",
"direction": 2
}]
}}
Here is the working code:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.google.gson.Gson;
public class GSONTest {
public static void main(String[] args){
String gsonStr = "{\"stationsInResponse\": { \"station62\":[ { \"departureTime\":1982,\"delay\":\"-1.0\",\"line\":\"6\",\"stationName\":\"randomname\",\"direction\":2} ],\"station63\":[ { \"departureTime\":1981,\"delay\":\"-1.1\",\"line\":\"7\",\"stationName\":\"randomname2\",\"direction\":3} ]}}";
Gson gson = new Gson();
Response response = gson.fromJson(gsonStr, Response.class);
System.out.println("Map size:"+response.getStationsInResponse().size());
for (Iterator iterator = response.getStationsInResponse().keySet().iterator(); iterator.hasNext();) {
String key = (String) iterator.next();
ArrayList<Station> stationList = (ArrayList<Station>) response.getStationsInResponse().get(key);
for (Iterator iterator2 = stationList.iterator(); iterator2.hasNext();) {
Station station = (Station) iterator2.next();
System.out.println("Delay: "+station.getDelay());
System.out.println("DepartureTime: "+station.getDepartureTime());
System.out.println("Line: "+station.getLine());
System.out.println("StationName: "+station.getStationName());
}
}
}
}
class Response {
private Map<String, List<Station>> stationsInResponse;
//getters and setters
public Map<String, List<Station>> getStationsInResponse() {
return stationsInResponse;
}
public void setStationsInResponse(Map<String, List<Station>> stationsInResponse) {
this.stationsInResponse = stationsInResponse;
}
}
class Station {
private String departureTime;
public String getDepartureTime() {
return departureTime;
}
public void setDepartureTime(String departureTime) {
this.departureTime = departureTime;
}
public String getDelay() {
return delay;
}
public void setDelay(String delay) {
this.delay = delay;
}
public String getLine() {
return line;
}
public void setLine(String line) {
this.line = line;
}
public String getStationName() {
return stationName;
}
public void setStationName(String stationName) {
this.stationName = stationName;
}
public String getDirection() {
return direction;
}
public void setDirection(String direction) {
this.direction = direction;
}
private String delay;
private String line;
private String stationName;
private String direction;
}
Output in console is like this(as I shortened your json string):
Map size:2
Delay: -1.0
DepartureTime: 1982
Line: 6
StationName: randomname
Delay: -1.1
DepartureTime: 1981
Line: 7
StationName: randomname2
First I'll point out your mistake, then I'll give you the solution.
The structure you're asking for in your deserialization code looks like this:
{
"stationsInResponse": {
"station1": [
{
"name": "Station 1"
}
],
"station2": [
{
"name": "Station 2"
}
]
}
}
Solution
The deserialization code you really need to deserialize the structure you're getting as input, is as follows:
Gson gson = new Gson();
Type rootType = new TypeToken<Map<String, List<Station>>>(){}.getType();
Map<String, List<Station>> stationsMap = gson.fromJson(json, rootType);
This is because a JSON object, whose properties are unknown at compile-time, which your root object is, maps to a Java Map<String, ?>. The ? represent the expected Java type of the JSON object value, which in your case is either List<Station> or Station[], whichever you prefer.
If you wanted to, you could combine all the stations in the map into one List of stations like so:
List<Station> stations = new ArrayList<>(stationsMap.size());
for (List<Station> stationsList : stationsMap.values()) {
for (Station station : stationsList) {
stations.add(station);
}
}
I have a Json Structure something like this
{
"name" : "abcd",
"details" : [{"city":"string", "zipcode":"integer"}],
"name" : "qwert",
"details" : [{"address":"long", "state":"string"}]
}
And my java code looks like below
public class JsonTest {
public static void main(String[] args) throws JsonParseException, IOException {
JsonFactory jf = new JsonFactory();
JsonParser jp = jf.createParser(new File("C:\\sample.json"));
while (jp.nextToken() != JsonToken.END_OBJECT)
{
String jsonField = jp.getCurrentName();
if ("name".equalsIgnoreCase(jsonField))
{
jp.nextToken();
System.out.println(jp.getText());
}
if ("details".equalsIgnoreCase(jsonField))
{
jp.nextToken();
while (jp.nextToken() != JsonToken.END_ARRAY)
{
jp.nextToken();
String field = jp.getText();
System.out.println(field);
}
}
}
}
}
All am trying to do is parse the whole json token by token and get the text. But the line while (jp.nextToken() != JsonToken.END_OBJECT) fails when it encounters a '}' in the array. Am just stuck here since yesterday and experimenting with several other tricks but nothing works out. Do we have another way doing this? Is my JSON structure looks okay? Please tell me where am going wrong. Thanks!
First of all:
[Doe]s my JSON structure look[s] okay?
Yes and no. while your JSON is legal, you are going to have a problem because of duplicate keys:
{
"name": "xxx",
"name": "yyy"
}
The behaviour of a JSON parser in this event is unpredictable, even RFC 7159 says so (yes, RFC 4627 has been superseded). Unfortunately, duplicate keys are still not forbidden.
So, it is legal, but not sane. Use an array instead:
[ { "name": "xxx", "details": "whatever" }, "etc" ]
This aside, reading JSON with Jackson is done as this:
final ObjectMapper mapper = new ObjectMapper();
final JsonNode node = mapper.readTree(yourFileHere);
You can then use the full power of JsonNode.
You cannot have fields with the same name in a json, well you can but it could not be parsed as in your case.
I assume you have an array of users with name and details fileds for each user.
Here is a candidate Java class corresponding to your User class:
public class TestUser implements Serializable {
String name;
List<Map<String, String>> details = new ArrayList<Map<String, String>>();
TestUser() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Map<String, String>> getDetails() {
return details;
}
public void setDetails(List<Map<String, String>> details) {
this.details = details;
}
}
with reverse engineering lets see how is a list of users are de-serialized;
public static void main(String[] args) throws IOException {
List<TestUser> testUsers = new ArrayList<TestUser>();
TestUser testUser1 = new TestUser();
testUser1.name = "abcd";
HashMap<String, String> detailsHashMap = new HashMap<String, String>();
detailsHashMap.put("city", "string");
detailsHashMap.put("zipcode", "integer");
testUser1.details.add(detailsHashMap);
testUsers.add(testUser1);
TestUser testUser2 = new TestUser();
testUser2.name = "qwert";
detailsHashMap = new HashMap<String, String>();
detailsHashMap.put("address", "long");
detailsHashMap.put("state", "string");
testUser2.details.add(detailsHashMap);
testUsers.add(testUser2);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(testUsers));
}
and the outcome of the users list is:
[ {
"name" : "abcd",
"details" : [ {
"zipcode" : "integer",
"city" : "string"
} ]
}, {
"name" : "qwert",
"details" : [ {
"address" : "long",
"state" : "string"
} ]
} ]
As you can see it is different from your JSON string.
UPDATE
If you want to hold names and details in two separate containers...;
public static class Container {
List<String> names = new ArrayList<String>();
Map<String, String> details = new HashMap<String, String>();
public Container() {
}
public List<String> getNames() {
return names;
}
public void setNames(List<String> names) {
this.names = names;
}
public Map<String, String> getDetails() {
return details;
}
public void setDetails(Map<String, String> details) {
this.details = details;
}
}
....
public static void main(String[] args) throws IOException {
Container container = new Container();
container.names.add("abcd");
container.names.add("qwert");
container.details.put("city", "string");
container.details.put("zipcode", "integer");
container.details.put("address", "long");
container.details.put("state", "string");
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(container));
}
and the outcome JSON is;
{
"names" : [ "abcd", "qwert" ],
"details" : {
"address" : "long",
"zipcode" : "integer",
"state" : "string",
"city" : "string"
}
}