I have a JSON with this content:
[ {
"lines" : {
"0" : "Hammersmith & City",
"1" : "Circle"
},
"id" : "233",
"name" : "Shepherd's Bush Market"
}, {
"lines" :"",
"id" : "233",
"name" : "Shepherd's Bush Market"
}, {
"lines" : {
"0" : "Hammersmith & City",
"1" : "Circle"
},
"id" : "233",
"name" : "Shepherd's Bush Market"
},
, {
"lines" : "",
"id" : "233",
"name" : "Shepherd's Bush Market"
}]
Normally, I could create an object like this
public class MyObject {
public String id;
public String name;
public Line[] lines;
public class Line {
public String key;
public String value;
}
}
And the Gson serializer would handle the parsing, but in this case lines doesn't have any keys/ids. I have tried using HashMaps and Maps instead of inner classes, but it doesn't work. Is there a way I can parse this using Gson?
UPDATE:
I have changed lines from MyObject to a Map<String, String> and added some more lines to JSON response
At the moment this is the code I'm using to parse the JSON
Type listType = new TypeToken<List<MyObject>>(){}.getType();
List<MyObject> data = getGson().fromJson(str, listType);
Caused by: com.google.gson.JsonParseException: The JsonDeserializer MapTypeAdapter failed to deserialize json object "" given the type java.util.Map<java.lang.String, java.lang.String>
After looking through the entire JSON response, it seems that lines is returned as a empty String ("") when it's not available and as a map when it is. I think this may be part of the problem
Use Map<String, String> instead Line[] lines.
(You don't need class Line)
It should work.
Or if your keys are integers Map<Integer, String> will work as well
[Edit]
Your json String represents list of objects: {..},{..},{..}
You need wrap it with [].
So the working json should be:
[
{
"lines": {
"0": "Hammersmith & City",
"1": "Circle"
},
"id": "233",
"name": "Shepherd's Bush Market"
},
{
"lines": {
"0": "Hammersmith & City",
"1": "Circle"
},
"id": "233",
"name": "Shepherd's Bush Market"
},
{
"lines": {
"0": "Hammersmith & City",
"1": "Circle"
},
"id": "233",
"name": "Shepherd's Bush Market"
}
]
MyObject
public class MyObject {
public String id;
public String name;
public Map<String,String> lines;
}
main method
Gson gson = new Gson();
Type type = new TypeToken<List<MyObject>>(){}.getType();
List<MyObject > objList = gson.fromJson(str, type);
assert(objList != null); // validate not null
for(MyObject obj : objList){
System.out.println("id=" + obj.id + "; name=" + obj.name);
}
Output:
id=233; name=Shepherd's Bush Market
id=233; name=Shepherd's Bush Market
id=233; name=Shepherd's Bush Market
in the loop you can extract Map as well
I like Maxim's solution for simplicity and +1 for him. Bur there is also other, little more complex way of doing it. In Lines class you can write keys as _0, _1
class Lines {
private String _0;
private String _1;
//#Override
//public String toString() {
// return "Lines [0=" + _0 + ", 1=" + _1 + "]";
//}
}
and use it in MyObject like
class MyObject {
private Lines lines;
private String id;
private String name;
//#Override
//public String toString() {
// return "Example [lines=" + lines + ", id=" + id + ", name=" + name + "]";
//}
}
After that you will have to create FieldNamingStrategy that would remove _ from numeric key.
class MyNameStrategy implements FieldNamingStrategy {
static Pattern numericPattern = Pattern.compile("_\\d+");
#Override
public String translateName(Field f) {
if (numericPattern.matcher(f.getName()).matches()){
return f.getName().substring(1);
}else{
return f.getName();
}
}
}
To use this strategy you need to create Gson via GsonBuilder
Gson gson = new GsonBuilder().setFieldNamingStrategy(
new MyNameStrategy()).create();
//...
MyObject[] arr = gson.fromJson(jsonString, MyObject[].class);
System.out.println(Arrays.toString(arr));
Also you are right that your JSon have problem in "lines" : "". Since you are placing there object (data inside {...}) you can't change your format later to string (which is not object in JSon format).
So if you can change your JSon you should replace "lines" : "" with either
"lines" : null
or "lines" : {}.
Related
I need to flatten a JSON strings(nested) using Gson ,
Given:
{
"name": "Terry",
"addr": {
"city": "NY",
"postCode": 123
},
"friends": ["John", "Paul"],
"nestedObject": { "a": { "b": { "c": {"d": "nested property" } } } }
}
Expected:
{
"name" :"Terry",
"addr.city": "NY",
"addr.postCode" : 123,
"friends.0": "John",
"friends.1": "Paul",
"nestedObject.a.b.c.d": "nested property"
}
or
{
"name" :"Terry",
"addr.city": "NY",
"addr.postCode" : 123,
"friends[0]": "John"
"friends[1]": "Paul"
"nestedObject.a.b.c.d": "nested property"
}
Really new to Java and need guidance how to build it up ,
My starting point :
1.Parse the Json using JsonParser as TreeModel
https://www.tutorialspoint.com/gson/gson_tree_model.htm
just found that JsonParser is deprecated :(
https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/JsonParser.html
is there alternative ?
2.Recursive function to loop through the Json elements and check
if element is list/dict - case yes , recursive call with all child ,
case else - append current key and val to some map
real pseudo code:
public class Test{
public static void main(String[] args) {
String jsonString = "{\"brand\" : \"Toyota\", \"doors\" : 5}";
JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject();
// Iterate through main key and call FlatJson for each one
// append result to a map
//print result
FlatJson - for given element ,
extract all child key
public String FlatJson( JsonElement jsonElement) {
// If input is an array, iterate through each element
if (jsonElement.isJsonArray()) {
for (JsonElement jsonElement1 : jsonElement.getAsJsonArray()) {
// Extract Key Value and return
}
} else {
// If input is object, iterate through the keys
if (jsonElement.isJsonObject()) {
Set<Map.Entry<String, JsonElement>> entrySet = jsonElement
.getAsJsonObject().entrySet();
for (Map.Entry<String, JsonElement> entry : entrySet) {
// Extract Key Value and return
}
}
}
// If input is element, check whether it corresponds to the key
else {
// return sonElement.toString() (as
}
}
}
}
I want to add a new field to jsonObject and this new field's name will be based on a value of another field. To be clear, this an examples of what I want to achieve.
{
"values": [
{
"id": "1",
"properties": [
{
"stat": "memory",
"data": 8
},
{
"stat": "cpu",
"data": 4
}
]
},
{
"id": "2",
"properties": [
{
"stat": "status",
"data": "OK"
},
{
"stat": "cpu",
"data": 4
}
]
}
]
}
I want to add a new field to each json object that will have the value of field "stat" as name.
{
"values": [
{
"id": "1",
"properties": [
{
"stat": "memory",
"data": 8,
"memory": 8
},
{
"stat": "cpu",
"data": 4,
"cpu": 4
}
]
},
{
"id": "2",
"properties": [
{
"stat": "status",
"data": 0,
"status": 0
},
{
"stat": "cpu",
"data": 4,
"cpu": 4
}
]
}
]
}
I have tried to do the following with JsonPath library but for me it's an ugly solution as I will parse the json three times and I do some manual replacements.
val configuration = Configuration.builder().options(Option.DEFAULT_PATH_LEAF_TO_NULL, Option.ALWAYS_RETURN_LIST).build()
val jsonContext5 = JsonPath.using(configuration).parse(jsonStr)
val listData = jsonContext.read("$['values'][*]['properties'][*]['data']").toString
.replace("[", "").replace("]", "").split(",").toList
val listStat = jsonContext.read("$['values'][*]['properties'][*]['stat']").toString
.replace("[", "").replace("]", "")
.replace("\"", "").split(",").toList
// Replacing values of "stat" by values of "data"
jsonContext5.map("$['values'][*]['properties'][*]['stat']", new MapFunction() {
var count = - 1
override def map(currentValue: Any, configuration: Configuration): AnyRef = {
count += 1
listData(count)
}
})
// replace field stat by its value
for( count <- 0 to listStat.size - 1){
val path = s"['values'][*]['properties'][$count]"
jsonContext5.renameKey(path, "stat", s"${listStat(count)}")
}
This is the result obtained
{
"values": [
{
"id": "1",
"properties": [
{
"data": 8,
"memory": "8"
},
{
"data": 4,
"cpu": "4"
}
]
},
{
"id": "2",
"properties": [
{
"data": 0,
"memory": "0"
},
{
"data": 4,
"cpu": "4"
}
]
}
]
}
Is there any better method to achieve this result ? I tried to do it with gson but it's not good handling paths.
This a way to do it with Gson but I will lose the information about other columns since I'm creating another json.
val jsonArray = jsonObject.get("properties").getAsJsonArray
val iter = jsonArray.iterator()
val agreedJson = new JsonArray()
while(iter.hasNext) {
val json = iter.next().getAsJsonObject
agreedJson.add(replaceCols(json))
}
def replaceCols(json: JsonObject) = {
val fieldName = "stat"
if(json.has(fieldName)) {
val columnName = json.get(fieldName).getAsString
val value: String = if (json.has("data")) json.get("data").getAsString else ""
json.addProperty(columnName, value)
}
json
}
How about something like this?
private static void statDup(final JSONObject o) {
if (o.containsKey("properties")) {
final JSONArray a = (JSONArray) o.get("properties");
for (final Object e : a) {
final JSONObject p = (JSONObject) e;
p.put(p.get("stat"), p.get("data"));
}
} else {
for (final Object key : o.keySet()) {
final Object value = o.get(key);
if (value instanceof JSONArray) {
for (final Object e : (JSONArray) value) {
statDup((JSONObject) e);
}
}
}
}
}
Using Gson, what you should do is create a base class that represents your initial JSON object. Then, extend that class and add the additional attribute(s) you want to add, such as "stat". Then, load the JSON objects into memory, either one by one or all together, then make the necessary changes to each to encompass your changes. Then, map those changes to the new class if you didn't in the prior step, and serialize them to a file or some other storage.
This is type-safe, a pure FP circe implementation with circe-optics:
object CirceOptics extends App {
import cats.Applicative
import cats.implicits._
import io.circe.{Error => _, _}
import io.circe.syntax._
import io.circe.parser._
import io.circe.optics.JsonPath._
val jsonStr: String = ???
def getStat(json: Json): Either[Error, String] =
root.stat.string.getOption(json)
.toRight(new Error(s"Missing stat of string type in $json"))
def getData(json: Json): Either[Error, Json] =
root.data.json.getOption(json)
.toRight(new Error(s"Missing data of json type in $json"))
def setField(json: Json, key: String, value: Json) =
root.at(key).setOption(Some(value))(json)
.toRight(new Error(s"Unable to set $key -> $value to $json"))
def modifyAllPropertiesOfAllValuesWith[F[_]: Applicative](f: Json => F[Json])(json: Json): F[Json] =
root.values.each.properties.each.json.modifyF(f)(json)
val res = for {
json <- parse(jsonStr)
modifiedJson <- modifyAllPropertiesOfAllValuesWith { j =>
for {
stat <- getStat(j)
data <- getData(j)
prop <- setField(j, stat, data)
} yield prop
} (json)
} yield modifiedJson
println(res)
}
The previous answer from Gene McCulley gives a solution with Java and using class net.minidev.json. This answer is using class Gson and written in Scala.
def statDup(o: JsonObject): JsonObject = {
if (o.has("properties")) {
val a = o.get("properties").getAsJsonArray
a.foreach { e =>
val p = e.getAsJsonObject
p.add(p.get("stat").getAsString, p.get("data"))
}
} else {
o.keySet.foreach { key =>
o.get(key) match {
case jsonArr: JsonArray =>
jsonArr.foreach { e =>
statDup(e.getAsJsonObject)
}
}
}
}
o
}
Your task is to add a new field to each record under each properties in the JSON file, make the current stat value the field name and data values the new field values. The code will be rather long if you try to do it in Java.
Suggest you using SPL, an open-source Java package to get it done. Coding will be very easy and you only need one line:
A
1
=json(json(file("data.json").read()).values.run(properties=properties.(([["stat","data"]|stat]|[~.array()|data]).record())))
SPL offers JDBC driver to be invoked by Java. Just store the above SPL script as addfield.splx and invoke it in a Java application as you call a stored procedure:
…
Class.forName("com.esproc.jdbc.InternalDriver");
con= DriverManager.getConnection("jdbc:esproc:local://");
st = con.prepareCall("call addfield()");
st.execute();
…
I have these sample records from database which is a result of group by query:
[
{
"name": "Troy",
"acct": 1123,
"type": " Savings",
"total": 50
},
{
"name": "Larry",
"acct": 4233,
"type": " Savings",
"total": 200
},
{
"name": "Troy",
"acct": 1123,
"type": " Current",
"total": 120
},
{
"name": "Larry",
"acct": 4233,
"type": " Current",
"total": 220
}
]
Now, i need to create a report that looks like so:
[
{
"name": "Troy",
"acct": 1123,
"totalSavings": 50,
"totalCurrent": 120
},
{
"name": "Larry",
"acct": 4233,
"totalSavings": 200,
"totalCurrent": 220
}
]
.
public class DbTrans {
private String name;
private String acct;
private String type;
private double total;
// getters and setters
...
}
I have tried using some lambda techniques like the one below, but i'm still not getting close to the solution i desire.
Map<String, List<DbTrans>> collect = transList.stream().collect(
Collectors.groupingBy(f -> f.getType()));
First of all the response Dto is not the same as the request Dto, what I suggest is to create a new Response class lests call it:
public class DbTransResponse {
private String name;
private String acct;
private double totalSavings;
private double totalCurrent;
// getters and setters
}
then the result can be like so :
List<DbTransResponse> result = transList.stream()
.collect(Collectors.groupingBy(DbTrans::getAcct))
.values().stream()
.map(trans -> new DbTransResponse(
trans.get(0).getName(),
trans.get(0).getAcct(),
trans.get(0).getTotal(),
trans.get(1).getTotal()
)).collect(Collectors.toList());
I consider that the list should contain two entries of each name so the you can get totalSavings from the first entry trans.get(0).getTotal() and totalCurrent from the second trans.get(1).getTotal().
If you are not sure you can use conditions to fill your object for example you can check if there are two elements if not set a default value.
Ideone demo
Outputs
DbTransResponse{name='Larry', acct='4233', totalSavings=200.0, totalCurrent=220.0}
DbTransResponse{name='Troy', acct='1123', totalSavings=50.0, totalCurrent=120.0}
you can use Collectors::toMap for this purpose (with a single collect operation)
Map<Integer, DbTransResponse> collect = transList.stream()
.collect(Collectors.toMap(DbTrans::getAcct,
DbTransResponse::new,
DbTransResponse::merge));
Collection<DbTransResponse> result = collect.values();
here is merge method in DbTransResponse class
static DbTransResponse merge(DbTransResponse r1, DbTransResponse r2) {
return new DbTransResponse(
r1.name, r1.acct,
r1.totalSavings + r2.totalSavings,
r1.totalCurrent + r2.totalCurrent
);
}
and an additional constructor for DbTransResponse class, though you can move this logic to a method
DbTransResponse(DbTrans dbTrans) {
this.name = dbTrans.getName();
this.acct = dbTrans.getAcct();
this.totalSavings = "Savings".equals(dbTrans.getType()) ? dbTrans.getTotal() : 0;
this.totalCurrent = "Current".equals(dbTrans.getType()) ? dbTrans.getTotal() : 0;
}
demo
Below is my JSON:
{
"key1" : "10.5",
"key2" : "20.5",
"key3" : "10.5",
"key4" : "20.5",
"key5" : "10.5"
}
I want to send this Json to the controller class while calling post service & I want to iterate json based on the key in the class. How do we receive the json in our class & How do we iterate.
I did the below:
#PostMapping("/api/v1/save")
public String save_commodities_user_predict_data(#RequestBody String jsonData) {
System.out.println("jsonData:"+jsonData);
}
In the console, I am getting below output:
jsonData:{
"key1" : "10.5",
"key2" : "20.5",
"key3" : "10.5",
"key4" : "20.5",
"key5" : "10.5"
}
Now, How do I iterate jsonData and get the value based on the key in my java class?
Please clarify.
Change your parameter type to Map<String, String> and iterate over the map. If there's a reason you can't do that, you'll have to parse the string into a json.
#PostMapping("/api/v1/save")
public String save_commodities_user_predict_data(#RequestBody Map<String, String> jsonData) {
for (Entry<String,String> pair : jsonData.entrySet()){
//iterate over the pairs
System.out.println(pair.getKey()+" "+pair.getValue());
}
}
I would change a little bit
public class MyClass{
private String key;
private Integer value;
}
#PostMapping("/api/v1/save")
public String save_commodities_user_predict_data(#RequestBody List<MyClass> myClass) {
myClass.forEach( c -> {
System.out.println(c.getKey() + c.getValue());
});
}
Json:
{[{
"key": "key1",
"value": 10.0
},
{
"key": "key2",
"value": 10.0
}]}
Hey there you can you the libraries GSON ou Jackson.
First create the Beans that haves the same properties as your Json, for example:
{"name":"John", "age":"24"}
class Person {
private String name;
private String age;
//getters and setters
}
But you can find more information in the libraries documentation
Hi I've just started to use Json.
My problem is I want json array in following form
[ { "id" : "1", "name" : "India" },{ "id" : "2", "name" : "Pakistan" },{ "id" : "3", "name" : "China" },{ "id" : "4", "name" : "Japan" },{ "id" : "5", "name" : "Russia" } ]
I want id and name title for every value.
Then biggest problem is when I am sending this json to ajax using servlet I am getting nothing (using this code)
List<stateList> sl = new ArrayList<stateList>();//ststeList is getters n setters obj
sl.add(new stateList("1","India"));
Gson js = new Gson();
js.toJson(sl);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(js.toString());
but if I use string object I am getting the value but without titles i.e (id,name)
{"1":"India","2":"Pak","3":"China"}
Code is
Map<String,String> m = new HashMap<String, String>();
m.put("1", "India");
m.put("2", "Pak");
m.put("3", "China");
String js = new Gson().toJson(m);
So finally I want above most json to send to ajax.There is no proble with ajax code its working fine with this type
Use entrySet to get the keys. Just loop through the entries
Code :
JsonParser p = new JsonParser();
JsonObject result = p.parse(file).getAsJsonObject();
Set<Map.Entry<String, JsonElement>> entrySet = result.entrySet();
for(Map.Entry<String, JsonElement> entry : entrySet) {
System.out.println(entry.getKey()); //this gives you the keys.
}
I hope this helps you
Ok after lots of toiling I got the solution which is already hidden in the class I built previously.
class stateList
{
private String id;
private String StateName;
stateList s;
public stateList(String id, String StateName)
{
this.id = id;
this.StateName = StateName;
}
public String toString() {
return "id = " +id+ ", stateName = " +StateName; //solution
}
}
just call the stateList's toString();
//code 2 line 5
js = new Gson().toJson(sl.toString());