I recently started working on an app that does a request to a server and gets a json response.
The "thing" functioned beautifully until i had to implement new stuff in the list and now i have a hard time to fix it.
Any help is very appreciated:
class RemoteConfig
{
// names and type must match what we get from the remote
String[] username;
ArrayList<accDetails> in_groups;
String[] in_groups_sorted;
class accDetails
{
int group_id;
String group_label;
Boolean _is_system;
}
This is just a part of how the class starts, and here is how the json reponse looks like:
{
"username":[
"mike"
],
"in_groups":[
{
"group_id":2,
"group_label":"All users",
"_is_system":true
},
{
"group_id":4372,
"group_label":"Privileged User",
"_is_system":false
},
{
"group_id":4979,
"group_label":"Supervisor",
"_is_system":false
}
]
}
The problem that i encounter now, is that i have no idea on how to split the in_groups array list and get into String[] in_groups_sorted the value of Group_label if the _is_system value is false.
Any help is highly appreciated.
Thank you,
Mike
After checking the responses, the cleanest and simplest was the one provided by Abbe:
public String[] groupSettings()
{
String[] levels = new String[] {};
if (remoteConfig != null && remoteConfig.in_groups != null){
for (accDetails ad: remoteConfig.in_groups)
{
if (!ad._is_system) {
levels = ArrayUtils.addAll(levels, ad.group_label); ;
}
}
}
return levels;
}
From your question, I suppose the JSON is already parsed and stored in the in_groups field of RemoteConfig class. And you just need to filter the information you need to populate the in_group_sorted field.
Add the following to the RemoteConfig class:
public initGroupSorted() {
// Temporary list, since we don't know the size once filtered
List<String> labels = new ArrayList<>();
for (accDetails ad : in_groups) {
if (ad._is_system) {
groups.add(ad.group_label);
}
}
in_group_sorted = labels.toArray(new String[labels.size()]);
}
if you don´t want to change the way you parse your JSON, you could always do this:
Let accDetails implement Comparable and then use Collections.sort passing in_groups.
if you really want the String[] you could always iterate over in_groups, add to in_groups_sorted and then using Arrays.sort
Mike, let me give you something that should get you going. From your question i got the feeling that your problem was on how to parse the JSON, so before you go write your own parser, consider the following piece of code that i just wrote:
public void createObjects(String rawJSON) {
try {
JSONObject object = new JSONObject(rawJSON);
JSONArray username = object.getJSONArray("username");
JSONArray inGroups = object.getJSONArray("in_groups");
RemoteConfig config = new RemoteConfig();
config.in_groups = new ArrayList<>();
config.username = username.getString(0);
for (int i = 0; i < inGroups.length(); i++) {
JSONObject group = inGroups.getJSONObject(i);
if (!group.getBoolean("_is_system")) {
accDetails details = new accDetails();
details.group_id = group.getInt("group_id");
details.group_label = group.getString("group_label");
details._is_system = false;
config.in_groups.add(details);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Here is a Java 8 Solution using Stream's filter,sorted, and map methods:
//ArrayList<accDetails> in_groups is already populated
Stream<accDetails> tempStream= in_groups.stream().filter(p -> p._is_system == false);
tempStream= tempStream.sorted((accDetails o1, accDetails o2) -> o1.group_label.compareTo(o2.group_label));
String[] in_groups_sorted = tempStream.map(s -> s.group_label).toArray(String[]::new);
Separated the calls for visibility, but they can be a one liner:
String[] in_groups_sorted = in_groups.stream().filter(p -> p._is_system == false).sorted((accDetails o1, accDetails o2) -> o1.group_label.compareTo(o2.group_label)).map(s -> s.group_label).toArray(String[]::new);
Related
My part of the task is:
I need to collect productId-s into list from two JSON files with different data and pass it into the custom method with directory of JSON files.
I have two JSON files with different data as the following:
JSON №1
JSON №2
And my code is in the next format:
public class Main {
public static void main(String[] args) throws IOException, ParseException {
JSONParser parser = new JSONParser();
InputStream isOne = JSONParser.class.getResourceAsStream("/test/java/resources/json/file/product_0001690510.json");
InputStream isTwo = JSONParser.class.getResourceAsStream("/test/java/resources/json/file/product_0001694109.json");
// ... need to use somehow InpuStream for reading two JSON files with different structure inside
JSONArray arr = obj.getJSONArray("products"); // notice that `"products": [...]`
String productId = null;
for (int i = 0; i < arr.length(); i++) {
productId = arr.getJSONObject(i).getString("productID");
}
List<String> productIds = new ArrayList<>(Collections.singleton(productId));
for (var file : obj.keySet()) {
System.out.println(getAllExportsWithProductIds(file, productIds));
}
}
public static List<String> getAllExportsWithProductIds(String directory, List<String> productIds) throws IOException {
var matchingObjects = new ArrayList<String>();
try (var fileStream = Files.walk(Path.of(directory))) {
for (var file : fileStream.toList()) {
var json = Json.readString(Files.readString(file));
var objects = JsonDecoder.array(json);
for (var object : objects) {
var objectProductIDs = JsonDecoder.field(
object, "products",
JsonDecoder.array(JsonDecoder.field("productID", JsonDecoder::string))
);
for (var productId : objectProductIDs) {
if (productIds.contains(productId)) {
matchingObjects.add(Json.writeString(object));
break;
}
}
}
}
}
return matchingObjects;
}
}
Full code
Based on it my question is:
Can I read all provided JSON files at the same time to collect productId-s into list? If yes, how can I do that if I have the different data of two JSON files.
To clarify a bit:
"By different data, I mean, in general, the number of these fields
(for example, productId fields in both of JSON-s: №1 and №2
accordingly), their order. That is, how an array object differs from
another.
If these JSON-s have the same data, it'd be easier to parse, but
in this specific usecase I need to find the way how to handle both of
them in different way as their data is not in the same format of representation."
My detailed explanation how I see it in abstract way:
How I see it abstractly in the lifecycle of developing.
I've already read different articles and checked libraries as:
java-jq
Jackson object mapper tutorial
and so on,
but I'm still struggling with understanding how to apply it correctly, for this reason, I'm looking for a concrete solution.
Thank you in advance for any smart and helpful ideas. If you need some additional details, I'm ready to provide without any problem.
UPD:
There's I have another version of code, which is collecting productID-s, I need just to find the way how to combine with the first version of code and that's it:
public class ProductIdImporter {
public ProductIdImporter() {
// TODO Auto-generated constructor stub
}
public void importJson() {
List<Path> paths = Arrays.asList(Paths.get("C:\\Users\\pc\\IdeaProjects\\jsonapi\\src\\test\\java\\resources\\json\\product_0001690510.json"),
Paths.get("C:\\Users\\pc\\IdeaProjects\\jsonapi\\src\\test\\java\\resources\\json\\product_0001694109.json"));
ObjectMapper jsonMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Set<String> productIds = paths.stream().map(path -> {
try {
return jsonMapper.readValue(Files.newInputStream(path), ExportList[].class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}).map(Arrays::asList)
.flatMap(List::stream)
.map(ExportList::productList)
.flatMap(List::stream)
.map(Product::getId)
.collect(Collectors.toSet());
productIds.forEach(System.out::println);
}
public static void main(String[] args) {
ProductIdImporter importer = new ProductIdImporter();
importer.importJson();
}
static class ExportList {
public List<Product> products;
public ExportList() {
}
public List<Product> productList() {
return products;
}
}
static class Product {
public String productID;
public Product() {
}
public String getId() {
return productID;
}
}
}
Taking the JSON №1 you have included in your post basically it is an array of json objects with the same format like below (I have included just the first one element and deleted all the properties you are not interested to parse):
[{
"products": [
{
"colorWayID": "IMP5002620012114",
"productID": "0001755256"
},
{
"colorWayID": "IMP8473190012114",
"productID": "0001690510"
},
{
"colorWayID": "IMP9100570012114",
"productID": "0001700877"
}
],
...other properties excluded from parsing
}]
To iterate over this array you can read your json file into a ArrayNode and iterate over every element:
ArrayNode arrayNode = (ArrayNode) mapper.readTree(json);
for (int i = 0; i < arrayNode.size(); ++i) {/*inspecting products property*/}
The products property is an array of JsonNode containing the productID property you want to extract, you can use the JsonNode#get method to extract both the products and productID properties:
List<String> productIds = new ArrayList<>();
ArrayNode arrayNode = (ArrayNode) mapper.readTree(json);
for (int i = 0; i < arrayNode.size(); ++i) {
//products property is also an array
ArrayNode products =(ArrayNode) arrayNode.get(i).get("products");
for (int j = 0; j < products.size(); ++j) {
//adding productId to your list
productIds.add(products.get(j).get("productID").asText());
}
}
This process can be repeated for other json files with variations depending from the json structure contained in them, but substantially it remains the same.
So i've been trying to solve this issue for hours but cant seem to find an answer which would work.
i have an object array which stores flight information and i had to remove flights which had Valstybe: "Maldyvai"
so i made a new object array without them, but when i try to print it i get a memory location.
How do i convert the object array to string array?
even though i have a tostring method in my java class
package com.company;
import java.util.*;
import com.company.Isvestine.OroUostasKeleivis;
public class Main {
public static void main(String[] args) {
// write your code here
OroUostasKeleivis Keleiviai1 = new OroUostasKeleivis("Skrydis","Washington","JAV","Tomas","tomaitis","Washington",5465);
OroUostasKeleivis Keleiviai2 = new OroUostasKeleivis("Skrydis","Washington","Maldyvai","Tomas","tomaitis","Maldyvai",5466);
OroUostasKeleivis Keleiviai3 = new OroUostasKeleivis("Skrydis","Washington","JAV","Tomas","tomaitis","Washington",5467);
OroUostasKeleivis Keleiviai4 = new OroUostasKeleivis("Skrydis","Washington","Maldyvai","Tomas","tomaitis","Maldyvai",5468);
OroUostasKeleivis Keleiviai5 = new OroUostasKeleivis("Skrydis","Washington","JAV","Tomas","tomaitis","Washington",5469);
OroUostasKeleivis Keleiviai6 = new OroUostasKeleivis("Skrydis","Washington","Maldyvai","Tomas","tomaitis","Maldyvai",5470);
OroUostasKeleivis Keleiviai7 = new OroUostasKeleivis("Skrydis","Washington","JAV","Tomas","tomaitis","Washington",5475);
OroUostasKeleivis Keleiviai8 = new OroUostasKeleivis("Skrydis","Washington","Maldyvai","Tomas","tomaitis","Maldyvai",5476);
OroUostasKeleivis Keleiviai9 = new OroUostasKeleivis("Skrydis","Washington","JAV","Tomas","tomaitis","Washington",5477);
OroUostasKeleivis Keleiviai10 = new OroUostasKeleivis("Skrydis","Washington","JAV","Tomas","tomaitis","Washington",5488);
OroUostasKeleivis[] keleiviai = new OroUostasKeleivis[10];
keleiviai[0] = Keleiviai1;
keleiviai[1] = Keleiviai2;
keleiviai[2] = Keleiviai3;
keleiviai[3] = Keleiviai4;
keleiviai[4] = Keleiviai5;
keleiviai[5] = Keleiviai6;
keleiviai[6] = Keleiviai7;
keleiviai[7] = Keleiviai8;
keleiviai[8] = Keleiviai9;
keleiviai[9] = Keleiviai10;
for (OroUostasKeleivis keleiveliai:keleiviai) {
System.out.println(keleiveliai);
}
System.out.println("test debug");
OroUostasKeleivis[] keleiviaibemaldyvu = new OroUostasKeleivis[10];
for (int i = 0; i < 10; i++) {
}
System.out.println(IsstrintiMaldyvus(keleiviai));
String convertedStringObject = IsstrintiMaldyvus(keleiviai) .toString();
System.out.println(convertedStringObject );
}
static Object[] IsstrintiMaldyvus(OroUostasKeleivis[] keleiviai){
OroUostasKeleivis[] keleiviaiBeMaldyvu = new OroUostasKeleivis[10];
int pozicija = 0;
for ( OroUostasKeleivis keleiveliai: keleiviai) {
if (keleiveliai.getValstybe() != "Maldyvai"){
keleiviaiBeMaldyvu[pozicija] = keleiveliai;
pozicija++;
}
}
return keleiviaiBeMaldyvu;
}
}
but when i try to print it i get a memory location
Yes, you will NOT have result as you expected, especially calling toString() with any array. See documentation of java.lang.Object.toString() for more details.
So how can we solve problem?
first, override toString() method in OroUostasKeleivis like this:
class OroUostasKeleivis {
#Override
public String toString() {
// your implementation here
return null; // TODO: change here
}
}
Second, you may do either way:
If you're interested in just print out, you can do that with System.out.println(keleiveliai) in for-each loop like you do.
If you're interested in converting OroUostasKeleivis[] to String[], you can:
// this requires Java 8 or later
String[] converted = Arrays.asList(keleiviai)
.stream()
.map(OroUostasKeleivis::toString)
.toArray(String[]::new);
// then use `converted`
Use System.out.println(Arrays.toString(IsstrintiMaldyvus(keleiviai)))
https://www.geeksforgeeks.org/arrays-tostring-in-java-with-examples/
It will print the array contents similar to how ArrayList would get printed if it had the same content.
Think of it as:
[ obj1.toString(), obj2.toString(), ... ]
Using java.util.Arrays#stream(T[]) filter and convert object array to string array and use java.util.Arrays#toString(java.lang.Object[]) convert array to readable string.
final String[] oroUostasKeleivis = Arrays.stream(keleiviai)
.filter(
k -> k.getValStybe() != "Maldyvai"
)
// or other convert code
.map(OroUostasKeleivis::toString)
.toArray(String[]::new);
System.out.println(Arrays.toString(oroUostasKeleivis));
I need to write a code which would convert JSON file to CSV. The problem is in a format that the CSV file should look like.
Input json:
{
"strings":{
"1level1":{
"1level2":{
"1label1":"1value1",
"1label2":"1value2"
}
},
"2level1":{
"2level2":{
"2level3":{
"2label1":"2value1"
},
"2label2":"2value2"
}
}
}
}
And this is expected csv file for this json:
Keys,Default
1level1.1level2.1label1,1value1
1level1.1level2.1label2,1value2
2level1.2level2.2level3.2label1,2value1
2level1.2level2.2label2,2value2
I was trying to go through JSON file using recursion but this didn't work for me because of rewriting JSON object on each iteration and code was working only till the first value. Are there any suggestions about how can it be done?
Note: have tried to use different JSON libraries, so for now can be used any of them
UPDATE #1:
Non-working code example I was trying to use to go through JSON tree:
public static void jsonToCsv() throws JSONException {
InputStream is = MainClass.class.getResourceAsStream("/fromJson.json");
JSONTokener jsonTokener = new JSONTokener(is);
JSONObject jsonObject = new JSONObject(jsonTokener);
stepInto(jsonObject);
}
private static void stepInto(JSONObject jsonObject) {
JSONObject object = jsonObject;
try {
Set < String > keySet = object.keySet();
for (String key: keySet) {
object = object.getJSONObject(key);
stepInto(object);
}
} catch (JSONException e) {
Set < String > keySet = object.keySet();
for (String key: keySet) {
System.out.println(object.get(key));
}
e.printStackTrace();
}
}
UPDATE #2:
Another issue is that I will never know the names of the JSON object and count of child objects (update JSON and CSV examples as well to make the image more clear). All that is known, that it will always start with strings object.
Library used:
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180813</version>
</dependency>
So found a solution by myself:
public static void jsonToCsv() throws JSONException, IOException {
InputStream is = MainClass.class.getResourceAsStream("/fromJson.json");
JSONTokener jsonTokener = new JSONTokener(is);
JSONObject jsonObject = new JSONObject(jsonTokener).getJSONObject("strings");
builder = new StringBuilder();
while (!jsonObject.isEmpty()) {
stepInto(jsonObject);
}
String[] lines = builder.toString().split("\n"); // builder lines are in reverse order from expected so this array is used to reverse them
FileWriter csvWriter = new FileWriter("src/main/resources/toCsv.csv");
csvWriter.append("Keys,Default (en_US)\n");
for (int i = lines.length - 1; i >= 0; i--) {
csvWriter.append(lines[i]).append("\n");
}
csvWriter.flush();
csvWriter.close();
}
private static void stepInto(JSONObject jsonObject) {
for (String key: jsonObject.keySet()) {
Object object = jsonObject.get(key);
if (object instanceof JSONObject) {
builder.append(key).append(".");
stepInto(jsonObject.getJSONObject(key));
} else {
builder.append(key).append(",").append(object).append("\n");
jsonObject.remove(key);
break;
}
if (jsonObject.getJSONObject(key).isEmpty()) {
jsonObject.remove(key);
}
break;
}
}
I think you just missed keeping track of your result, otherwise it looks good.
Let's say your result is a simple string. Then you have to concatenate all keys while traversing the json object until you reach a primitive value (like a number or a string).
(I am writing this out of my head, so please forgive me for incorrect syntax)
private static String stepInto(JSONObject jsonObject) { // we change "void" to "String" so we can record the results of each recursive "stepInto" call
//JSONObject object = jsonObject; // we don't need that. Both variables are the same object
String result ="";
try {
for (String key: jsonObject.keySet()) { // shorter version
Object object = jsonObject.get(key); // Attention! we get a simple Java Object
if(object instanceof JSONObject){
result+= key+"."+stepInto(jsonObject.getJSONObject(key)); // the recursive call, returning all keys concatenated to "level1.level2.level3" until we reach a primitive value
}
if(object instanceof JSONArray){
result+= key+", "+ ... // notice how we use the csv separator (comma) here, because we reached a value. For you to decide how you want to represent arrays
}
result+= key +", "+ object +"\n"; // here I am not sure. It may well be that you need to check if object is a String an Integer, Float or anything.
}
} catch (JSONException e) {
for (String key: jsonObject.keySet()) {
System.out.println(object.get(key));
}
e.printStackTrace();
result+= "\n"; // I added this fallback so your program can terminate even when an error occurs.
}
return result; // sorry, I forgot to accumulate all results and return them. So now we have only one actual "return" statement that will terminate the call and return all results.
}
As you can see, I didn't change much of your original method. The only difference is that now we keep track of the keys ("level1.level2...") for each recursive call.
EDIT
I added a +"\n"; so everytime we reach a value so we can terminate that "line".
AND more importantly, instead of returning everytime, I add the result of each call to a string, so we continue looping over the keys and concatenate all results. Each call of the method will return only once all keys are looped over. (sorry that missed that)
In your calling method you could print out the result, something like that:
JSONObject jsonObject = new JSONObject(jsonTokener);
String result = stepInto(jsonObject);
System.out.println(result);
I have a small java app and I have used JInterface to essentially expose it as an OTP process in my elixir app. I can call it and get a response successfully.
My problem is that the response I get back in elixir is of a binary but I cannot figure out how to convert a binary to a list of strings which is what the response is.
The code for my OTP node in Java using JInterface is below:
public void performAction(Object requestData, OtpMbox mbox, OtpErlangPid lastPid){
List<String> sentences = paragraphSplitter.splitParagraphIntoSentences((String) requestData, Locale.JAPAN);
mbox.send(lastPid, new OtpErlangBinary(getOtpStrings(sentences)));
System.out.println("OK");
}
private List<OtpErlangString> getOtpStrings(List<String> sentences) {
List<OtpErlangString> erlangStrings = new ArrayList<>();
for(int i = 0; i < sentences.size(); i++){
erlangStrings.add(new OtpErlangString(sentences.get(i)));
}
return erlangStrings;
}
It is necessary to wrap the response in an OtpErlangBinary and I have concerted the strings to OTPErlangString. I have also tried without converting the strings to OTPErlangString.
On the elixir side I can receive the binary response and IO.inspect it.
Does anybody know how to use JInterface to deserialise the results correctly when it's anything other than a single string? Or maybe, if I have made some mistake, how to build the correct response type so that I can deserialise it correctly?
Any help would be really appreciated as I have been trying to figure this out for ages.
Thanks in advance.
I have been playing around with JInterface and Elixir and I think I've got your problem figured out.
So you are trying to send a list of strings from an Elixir/Erlang node to a Java node, but you cannot get it to de-serialize properly.
Elixir has its own types (e.g., atoms, tuples, ..) and Java has its own types (e.g., Object, String, List<String>,..). There needs to be a conversion from the one type to the other if they're supposed to talk to each other. In the end it's just a bunch of 1's and 0's that get sent over the wire anyway.
If an Erlang list is sent to Java, what arrives can always be interpreted as an OtpErlangObject. It's up to you to then try and guess what the actual type is before we can even begin turning it into a Java value.
// We know that everything is at least an OtpErlangObject value!
OtpErlangObject o = mbox.receive();
But given that you know that it's in fact a list, we can turn it into an OtpErlangList value.
// We know o is an Erlang list!
OtpErlangList erlList = (OtpErlangList) o;
The elements of this list however, are still unknown. So at this point its still a list of OtpErlangObjects.
But, we know that it's a list of strings, so we can interpret the list of OtpErlangObjects as list of OtpErlangStrings, and convert those to Java strings.
public static List<String> ErlangListToStringList(OtpErlangList estrs) {
OtpErlangObject[] erlObjs = estrs.elements();
List<String> strs = new LinkedList<String>();
for (OtpErlangObject erlO : erlObjs) {
strs.add(erlO.toString());
}
return strs;
}
Note that I used the term list here a lot, because it's in fact an Erlang list, in Java it's all represented as an array!
My entire code is listed below.
The way to run this is to paste it into a Java IDE, and start a REPL with the following parameters:
iex --name bob#127.0.0.1 --cookie "secret"
Java part:
import com.ericsson.otp.erlang.*;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class Main {
public static OtpErlangList StringListToErlangList(List<String> strs) {
OtpErlangObject[] elems = new OtpErlangObject[strs.size()];
int idx = 0;
for (String str : strs) {
elems[idx] = new OtpErlangString(str);
idx++;
}
return new OtpErlangList(elems);
}
public static List<String> ErlangListToStringList(OtpErlangList estrs) {
OtpErlangObject[] erlObjs = estrs.elements();
List<String> strs = new LinkedList<String>();
for (OtpErlangObject erlO : erlObjs) {
strs.add(erlO.toString());
}
return strs;
}
public static void main(String[] args) throws IOException, InterruptedException {
// Do some initial setup.
OtpNode node = new OtpNode("alice", "secret");
OtpMbox mbox = node.createMbox();
mbox.registerName("alice");
// Check that the remote node is actually online.
if (node.ping("bob#127.0.0.1", 2000)) {
System.out.println("remote is up");
} else {
System.out.println("remote is not up");
}
// Create the list of strings that needs to be sent to the other node.
List<String> strs = new LinkedList<String>();
strs.add("foo");
strs.add("bar");
OtpErlangList erlangStrs = StringListToErlangList(strs);
// Create a tuple so the other node can reply to use.
OtpErlangObject[] msg = new OtpErlangObject[2];
msg[0] = mbox.self();
msg[1] = erlangStrs;
OtpErlangTuple tuple = new OtpErlangTuple(msg);
// Send the tuple to the other node.
mbox.send("echo", "bob#127.0.0.1", tuple);
// Await the reply.
while (true) {
try {
System.out.println("Waiting for response!");
OtpErlangObject o = mbox.receive();
if (o instanceof OtpErlangList) {
OtpErlangList erlList = (OtpErlangList) o;
List<String> receivedStrings = ErlangListToStringList(erlList);
for (String s : receivedStrings) {
System.out.println(s);
}
}
if (o instanceof OtpErlangTuple) {
OtpErlangTuple m = (OtpErlangTuple) o;
OtpErlangPid from = (OtpErlangPid) (m.elementAt(0));
OtpErlangList value = (OtpErlangList) m.elementAt(1);
List<String> receivedStrings = ErlangListToStringList(value);
for (String s : receivedStrings) {
System.out.println(s);
}
}
} catch (OtpErlangExit otpErlangExit) {
otpErlangExit.printStackTrace();
} catch (OtpErlangDecodeException e) {
e.printStackTrace();
}
}
}
}
I have a JSON string that I get from a database which contains repeated keys. I want to remove the repeated keys by combining their values into an array.
For example
Input
{
"a":"b",
"c":"d",
"c":"e",
"f":"g"
}
Output
{
"a":"b",
"c":["d","e"],
"f":"g"
}
The actual data is a large file that may be nested. I will not know ahead of time what or how many pairs there are.
I need to use Java for this. org.json throws an exception because of the repeated keys, gson can parse the string but each repeated key overwrites the last one. I need to keep all the data.
If possible, I'd like to do this without editing any library code
As of today the org.json library version 20170516 provides accumulate() method that stores the duplicate key entries into JSONArray
JSONObject jsonObject = new JSONObject();
jsonObject.accumulate("a", "b");
jsonObject.accumulate("c", "d");
jsonObject.accumulate("c", "e");
jsonObject.accumulate("f", "g");
System.out.println(jsonObject);
Output:
{
"a":"b",
"c":["d","e"],
"f":"g"
}
I want to remove the repeated keys by combining their values into an array.
Think other than JSON parsing library. It's very simple Java Program using String.split() method that convert Json String into Map<String, List<String>> without using any library.
Sample code:
String jsonString = ...
// remove enclosing braces and double quotes
jsonString = jsonString.substring(2, jsonString.length() - 2);
Map<String, List<String>> map = new HashMap<String, List<String>>();
for (String values : jsonString.split("\",\"")) {
String[] keyValue = values.split("\":\"");
String key = keyValue[0];
String value = keyValue[1];
if (!map.containsKey(key)) {
map.put(key, new ArrayList<String>());
}
map.get(key).add(value);
}
output:
{
"f": ["g"],
"c": ["d","e"],
"a": ["b"]
}
In order to accomplish what you want, you need to create some sort of custom class since JSON cannot technically have 2 values at one key. Below is an example:
public class SomeClass {
Map<String, List<Object>> values = new HashMap<String, List<Object>>();
public void add(String key, Object o) {
List<Object> value = new ArrayList<Object>();
if (values.containsKey(key)) {
value = values.get(key);
}
value.add(o);
values.put(key, value);
}
public JSONObject toJson() throws JSONException {
JSONObject json = new JSONObject();
JSONArray tempArray = null;
for (Entry<String, List<Object>> en : values.entrySet()) {
tempArray = new JSONArray();
for (Object o : en.getValue()) {
tempArray.add(o);
}
json.put(en.getKey(), tempArray);
}
return json;
}
}
You can then retrieve the values from the database, call the .add(String key, Object o) function with the column name from the database, and the value (as the Object param). Then call .toJson() when you are finished.
Thanks to Mike Elofson and Braj for helping me in the right direction. I only wanted to have the keys with multiple values become arrays so I had to modify the code a bit. Eventually I want it to work for nested JSON as well, as it currently assumes it is flat. However, the following code works for what I need it for at the moment.
public static String repeatedKeysToArrays(String jsonIn) throws JSONException
{
//This assumes that the json is flat
String jsonString = jsonIn.substring(2, jsonIn.length() - 2);
JSONObject obj = new JSONObject();
for (String values : jsonString.split("\",\"")) {
String[] keyValue = values.split("\":\"");
String key = keyValue[0];
String value = "";
if (keyValue.length>1) value = keyValue[1];
if (!obj.has(key)) {
obj.put(key, value);
} else {
Object Oold = obj.get(key);
ArrayList<String> newlist = new ArrayList<String>();
//Try to cast as JSONArray. Otherwise, assume it is a String
if (Oold.getClass().equals(JSONArray.class)) {
JSONArray old = (JSONArray)Oold;
//Build replacement value
for (int i=0; i<old.length(); i++) {
newlist.add( old.getString(i) );
}
}
else if (Oold.getClass().equals(String.class)) newlist = new ArrayList<String>(Arrays.asList(new String[] {(String)Oold}));
newlist.add(value);
JSONArray newarr = new JSONArray( newlist );
obj.put(key,newarr);
}
}
return obj.toString();
}