Parsing unnamed nested arrays with minimal-json? - java

So I'm working on a fairly simple Java program which grabs market data from cryptocurrency exchanges and displays information to the user. I am using the minimal-json library.
Here is my current code:
public class Market {
static JsonArray arrayBittrex;
public static void startTimer(){
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
String url = "https://bittrex.com/api/v1.1/public/getmarketsummaries";
try {
URL url2 = new URL(url);
URLConnection con = url2.openConnection();
InputStream in = con.getInputStream();
String encoding = "UTF-8";
String body = IOUtils.toString(in, encoding);
arrayBittrex = Json.parse(body).asObject().get("result").asArray();
}
catch(MalformedURLException e) {}
catch(IOException e) {}
}
}, 0,5000);
}
public static float getPrice(String exchange, String market) {
for (JsonValue item : arrayBittrex) {
float last = item.asObject().getFloat("Last", 0);
System.out.println(last);
return last;
}
return 0;
}
}
This code works with simple json, for example (from https://bittrex.com/api/v1.1/public/getmarketsummary?market=btc-ltc):
{
"success" : true,
"message" : "",
"result" : [{
"MarketName" : "BTC-LTC",
"High" : 0.01350000,
"Low" : 0.01200000,
"Volume" : 3833.97619253,
"Last" : 0.01349998
}
]
}
It will properly return the "Last" value in the array.
However, this cant work when the json has multiple arrays (like in https://bittrex.com/api/v1.1/public/getmarketsummaries):
{
"success" : true,
"message" : "",
"result" : [{
"MarketName" : "BTC-888",
"High" : 0.00000919,
"Low" : 0.00000820,
"Volume" : 74339.61396015,
"Last" : 0.00000820
}, {
"MarketName" : "BTC-A3C",
"High" : 0.00000072,
"Low" : 0.00000001,
"Volume" : 166340678.42280999,
"Last" : 0.00000005
}
]
}
So my question is: how can I get the "Last" value by searching for the array by the "MarketName" value?

Here is a direct & null-safe way to tackle this using Java 8 library Dynamics. We're going to parse the json into a Map, read that map dynamically to what we want.
So first we can use Jackson, Gson or something to convert json -> map.
// com.fasterxml.jackson.core:jackson-databind json -> map
Map jsonMap = new ObjectMapper()
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
.readValue(jsonStringOrInputSourceEtc, Map.class);
We can now get a Dynamic instance. And, for example, grab the BTC-A3C - Last value.
Dynamic json = Dynamic.from(jsonMap);
BigDecimal a3cLast = json.get("result").children()
.filter(data -> data.get("MarketName").asString().equals("BTC-A3C"))
.findAny()
.flatMap(data -> data.get("Last").maybe().convert().intoDecimal())
.orElse(BigDecimal.ZERO);
// 5E-8
Or perhaps convert the whole lot into a map of MarketName -> Last value
Map<String, BigDecimal> marketNameLastValue = json.get("result").children()
// assume fields are always present, otherwise see #maybe() methods
.collect(toMap(
data -> data.get("MarketName").asString(),
data -> data.get("Last").convert().intoDecimal()
));
// {BTC-A3C=5E-8, BTC-888=0.00000820}
See more examples https://github.com/alexheretic/dynamics

Related

Retrofit - Deserilize Json response map into a property

I'm having a problem deserializing the response that I'm getting from retrofit.
My problem is that I get the following response:
"associations": {
"1": {
"uri": "https://api.ap.org/media/v/content/690b9f679b1d4d8abc8042ca53140625?qt=FckGhfkHkvF&et=0a1aza3c0&ai=881778bb579d79e17f54b046a86a81cf",
"altids": {
"itemid": "690b9f679b1d4d8abc8042ca53140625",
"etag": "690b9f679b1d4d8abc8042ca53140625_0a1aza3c0"
},
"version": 0,
"type": "picture",
"headline": "Facebook Ads-Targeting Info"
}
}
My entity looks like the following:
public class Associations{
private Map<String, JsonMember> association;
public Map<String, JsonMember> getAssociation() {
return association;
}
public void setAssociation(Map<String, JsonMember> association) {
this.association = association;
}
}
I want that association takes the value from the map, but I don't know how to specify that it takes the object inside that association. Those association keys can be returned as any number, so I can't hard code the "1".
Any ideas?
Thanks for your help!
You can read your json to a JsonNode and iterate over its properties names with a Iterator<String> iterator obtained calling the JsonNode#fieldNames method, selecting only the properties with a numeric name (this depends from what you mean for numeric, up to you the definition of a isNumber method):
Map<String, JsonMember> map = new HashMap<>();
//reading the jsonnode labelled with "associations"
JsonNode node = mapper.readTree(json).at("/associations");
Iterator<String> iterator = node.fieldNames();
while (iterator.hasNext()) {
String next = iterator.next();
if (isNumber(next)) { //<-- ok next is numeric
map.put(next, mapper.treeToValue(node.get(next), JsonMember.class));
}
}

Transform String into nested Json (Java)

I would like the guidence from you all, i'm confused in how to go on in a situation at Java + Spring Boot.
I receive from the database 2 columns of strings, the first column is a path separeted by a slash(/) like "crumbs[0]/link/path" and the second column have the value assigned to the first column, and what i'm trying to do is to create a nested JSON with this.
For example, i'm receiving from the database the following response in two columns like a said before:
COLUMN 1(PATH), COLUMN 2(VALUE)
"crumbs[0]/link/path", "/pokemon/type/pokemon?lang=en"
"crumbs[0]/link/wid", "tablePokemon",
"crumbs[0]/name", "Pokemon"
"data/records[1]/id", "Pikachu"
"data/records[1]/link/path": "/pokemon/type/eletric/pikachu",
"data/records[1]/link/wid": "tableEletric",
"data/records[1]/available": "true",
"data/records[2]/id", "Bulbasaur"
"data/records[2]/link/path": "/pokemon/type/grass/bulbasaur",
"data/records[2]/link/wid": "tableGrass",
"data/records[2]/available": "true",
With this response from database, i'm trying to get this result in Java:
"crumbs": [
{
"link": {
"path": "/pokemon/type/pokemon?lang=en",
"wid": "tablePokemon"
},
"name": "Pokemon"
}
],
"data": {
"records": [
{
"id": "Pikachu",
"link": {
"path": "/pokemon/type/eletric/pikachu",
"wid": "tableEletric"
},
"available": "true",
},
{
"id": "Bulbasaur",
"link": {
"path": "/pokemon/type/grass/bulbasaur",
"wid": "tableGrass"
},
"available": "true",
}
]
}
You guys would have any suggestions for me to achieve this objective?
Thank you all for your time, appreciate any help.
You can easily construct a JSON with com.fasterxml.jackson.core.JsonPointer!
Some details on parsing a JsonPath and constructing a Json from it is mentioned here How to add new node to Json using JsonPath?
You could made use of the code from the above reference to build your code.
Add the com.fasterxml.jackson dependencies to your pom.xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<version>1.1.2</version>
</dependency>
private static final ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) {
/** I'm creating a below map to hold the values you have mentioned in the above case.
While using JsonPointer I found two issues with the key mentioned here
1. Key doesnt start with a / . I'm appending a / with the key while inserting to map
2. The arrays in data eg:crumbs[0]/link/path should be represented like crumbs/0/link/path ( I haven't handled this in the code, but it doesn't make much difference in the output)
**/
Map<String, String> databaseKeyValues = new HashMap<String, String>();
databaseKeyValues.put("crumbs[0]/link/path", "/pokemon/type/pokemon?lang=en");
databaseKeyValues.put("crumbs[0]/link/wid", "tablePokemon");
databaseKeyValues.put("crumbs[0]/name", "Pokemon");
databaseKeyValues.put("data/records[1]/id", "Pikachu");
databaseKeyValues.put("data/records[1]/link/path", "/pokemon/type/eletric/pikachu");
databaseKeyValues.put("data/records[1]/link/wid", "tableEletric");
databaseKeyValues.put("data/records[1]/available", "true");
databaseKeyValues.put("data/records[2]/id", "Bulbasaur");
databaseKeyValues.put("data/records[2]/link/path", "/pokemon/type/grass/bulbasaur");
databaseKeyValues.put("data/records[2]/link/wid", "tableGrass");
databaseKeyValues.put("data/records[2]/available", "true");
ObjectNode rootNode = mapper.createObjectNode();
for(java.util.Map.Entry<String, String> e:databaseKeyValues.entrySet()) {
setJsonPointerValue(rootNode, JsonPointer.compile("/"+e.getKey()), //Adding slash to identify it as the root element, since our source data didn't have proper key!
new TextNode(e.getValue()));
}
try {
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode));
} catch (JsonProcessingException e1) {
e1.printStackTrace();
}
}
public static void setJsonPointerValue(ObjectNode node, JsonPointer pointer, JsonNode value) {
JsonPointer parentPointer = pointer.head();
JsonNode parentNode = node.at(parentPointer);
String fieldName = pointer.last().toString().substring(1);
if (parentNode.isMissingNode() || parentNode.isNull()) {
parentNode = mapper.createObjectNode();
setJsonPointerValue(node,parentPointer, parentNode); // recursively reconstruct hierarchy
}
if (parentNode.isArray()) {
ArrayNode arrayNode = (ArrayNode) parentNode;
int index = Integer.valueOf(fieldName);
// expand array in case index is greater than array size (like JavaScript does)
for (int i = arrayNode.size(); i <= index; i++) {
arrayNode.addNull();
}
arrayNode.set(index, value);
} else if (parentNode.isObject()) {
((ObjectNode) parentNode).set(fieldName, value);
} else {
throw new IllegalArgumentException("`" + fieldName + "` can't be set for parent node `"
+ parentPointer + "` because parent is not a container but " + parentNode.getNodeType().name());
}
}
Output:
{
"data" : {
"records[1]" : {
"id" : "Pikachu",
"link" : {
"wid" : "tableEletric",
"path" : "/pokemon/type/eletric/pikachu"
},
"available" : "true"
},
"records[2]" : {
"available" : "true",
"link" : {
"wid" : "tableGrass",
"path" : "/pokemon/type/grass/bulbasaur"
},
"id" : "Bulbasaur"
}
},
"crumbs[0]" : {
"link" : {
"path" : "/pokemon/type/pokemon?lang=en",
"wid" : "tablePokemon"
},
"name" : "Pokemon"
}
}
The json arrays records[1], records[2], crumbs[0] would be sorted out once we handle the JsonPath from crumbs[0]/link/path to crumbs/0/link/path. Just some string operations would help (iterate through the values and replace '[0]' with '/0/', you could write a regex to pattern match and replace!).
You will need to parse the paths, then build some kind of tree object in memory, and finally convert the tree that you built into JSON.
Here are some tips:
Start by defining an empty root element. You can use a Map. Keys will be strings, values will be either strings, lists or maps.
For each path, split it by "/".
For each path element except the last, check if it is a list or a subtree. You can distinguish this by the presence of [n] at the end of the string.
Create all intermediate nodes for the path except for the last one. Starting from root (which is a Map), add either a List or a Map for each element if it doesn't exist yet under that name. If it already exists, check that it is what you need it to be. In case of List, append the element. In case of Map, create a sub-entry.
For the last path element, add it as a String.
Repeat this for all paths to fill your tree.
When you are finished, use a combination of recursion and StringBuiders to construct the output string. Alternatively, if you only used strings, maps and lists, you can also use a library such as Jackson to produce JSON.
Note that you don't have information about the length of the lists, so this conversion will not be reversible.

How to Query ES using Rabbitmq spark stram

I am using Spark2. I am trying to get the stream of search text from Rabbitmq and query againt Elasticsearch.
params.put("hosts", "IP");
params.put("queueName", "query");
params.put("exchangeName", "Exchangequery");
params.put("vHost", "/");
params.put("userName", "test");
params.put("password", "test");
Function<byte[], String> messageHandler = new Function<byte[], String>() {
public String call(byte[] message) {
return new String(message);
}
};
JavaReceiverInputDStream<String> messages = RabbitMQUtils.createJavaStream(jssc, String.class, params, messageHandler);
messages.foreachRDD();
above code receives stram from rabbitmq. But i am not sure how to connect ES and query for the stream batch. One thing is, If i use messages.foreachRDD(); and query the elasticsearch for each input item then it will affect the performance.
Always i will query elasticsearch using only one field. For example
My stram messages has the input like
apple
orange
i have a index in es fruit and i want to query like ?q=apple or orange. I know i have to frame the query using should in elasticsearch. My question is how can i query against ES using the value received from Rabbitmq stream
The code makes only one call to the elasticsearch server (basically it constructs a single query with a lot of should clauses)
public static void main(String[] args) throws UnknownHostException {
Client client = TransportClient.builder().build()
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host1"), 9300))
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host2"), 9300));
List<String> messages = new ArrayList<>();
messages.add("apple");
messages.add("orange");
String index = "fruit";
String fieldName = "fruit_type";
BoolQueryBuilder query = QueryBuilders.boolQuery();
for (String message : messages) {
query.should(QueryBuilders.matchQuery(fieldName, message));
// alternative if you are not analyzing fields
// query.should(QueryBuilders.termQuery(fieldName, message));
}
int size = 60; //you may want to change this since it defaults to 10
SearchResponse response = client.prepareSearch(index).setQuery(query).setSize(size).execute().actionGet();
long totalHits = response.getHits().getTotalHits();
System.out.println("Found " + totalHits + " documents");
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getSource());
}
}
Query generated:
{
"bool" : {
"should" : [ {
"match" : {
"fruit_type" : {
"query" : "apple",
"type" : "boolean"
}
}
}, {
"match" : {
"fruit_type" : {
"query" : "orange",
"type" : "boolean"
}
}
} ]
}
}

retrieve array from mongodb using java with mongodb api

I understand that there are many questions which as for the same and they are answered well. The problem is all those questions use MongoDBObject, MongoDBList to retrieve arrays. My problem is I am using http://api.mongodb.org/java/3.0/index.html?overview-summary.html api and I am having hard time retrieving array and adding elements to it. I have to use MongoCollection, MongoDatabase and MongoClient. I am trying to solve an assignment from mongodb course. The problem statement is to find an array and update it in mongod.
Here is what I have tried
Document post = null; Bson filter = new Document("permalink",
permalink); Bson projection = new Document("comments", true);
List<Document> comments = postsCollection.find(filter)
.projection(projection).into(new ArrayList<Document>());
System.out.println(comments);
post = postsCollection.find(Filters.eq("permalink",
permalink)).first();
Document newComment = new Document();
newComment.append("author", name); newComment.append("body", body);
if (email != null && (!email.equals(""))) {
newComment.append("email", email); }
comments.add(newComment);
Bson filter2 = new Document("_id", post.get("_id"));
System.out.println(comments); post =
postsCollection.find(filter).first();
postsCollection.updateOne(filter2, new Document("$unset",new
Document("comments",true))); postsCollection.updateOne(filter2, new
Document("$set", new Document( "comments", comments)));
This does not create a new comment. Instead, it creates another comments array in comments array itself. THe array should be updated in student
Here is the json data
{
"_id" : ObjectId("55d965eee60dd20c14e8573e"),
"title" : "test title",
"author" : "prasad",
"body" : "test body",
"permalink" : "test_title",
"tags" : [
"test",
"teat"
],
"date" : ISODate("2015-08-23T06:19:26.826Z"),
"comments" : [
{
"_id" : ObjectId("55d965eee60dd20c14e8573e"),
"comments" : [
{
"_id" : ObjectId("55d965eee60dd20c14e8573e"),
"comments" : []
},
{
"author" : "commented",
"body" : "something in comment",
"email" : "some#thing.com"
}
]
},
{
"author" : "commented",
"body" : "something in comment",
"email" : "some#thing.com"
}
]
}
To avoid unchecked casts and linter warnings, along with writing your own loop, use the libary's get(final Object key, final Class<T> clazz) method:
List<Document> comments = posts.get("comments", docClazz)
where docClazz is something that you create once:
final static Class<? extends List> docClazz = new ArrayList<Document>().getClass();
You need not write to this much code. Please check following code,
public void addPostComment(final String name, final String email, final String body,
final String permalink) {
Document post = findByPermalink(permalink);
List<Document> comments = null;
Document comment = new Document();
if(post != null){
comments = (List<Document>)post.get("comments");
comment.append("author",name).append("body", body);
if(email != null){
comment.append("email", email);
}
comments.add(comment);
postsCollection.updateOne(new Document("permalink",permalink),
new Document("$set",new Document("comments",comments)));
}
}
This is much simplified here!
version - mongo-java-driver-3.12.5.jar
comments = post.getList("comments", Document.class);
If you're forced to use older version of mongo driver and you can't use the method the MKP has mentioned, then you can copy the method itself.
Here it is as a Kotlin extension
import org.bson.Document
import java.lang.String.format
fun <T> Document.getList(key: String, clazz: Class<T>, defaultValue: List<T>): List<T> {
val list = this.get(key, List::class.java)
if (list == null) {
return defaultValue
}
list.forEach {
if (!clazz.isAssignableFrom(it!!::class.java)) {
throw ClassCastException(format("List element cannot be cast to %s", clazz.getName()))
}
}
return list as List<T>
}

Android: KeyValuePairs to json

Is there some easy way of transforming KeyValuePairs into json
For Example
{ "keyvaluepairs" :
{ "long": "6,5",
"heavy": "200",
}
}
long and heavy are inside the keyvaluepairs? some easyway of doing this?
I'm not sure exactly what data structure you mean by KeyValuePairs, but presumably you can do something like:
JSONObject object = new JSONObject("keyvaluepairs");
for (Entry entry : keyValuePairs) {
object.put(entry.getKey(), entry.getValue();
}

Categories

Resources