I am writing my own JSON parser in Java and I am looking for a way to split a string by a comma or a colon, that are outside [], {} or "" pairs.
I found a tutorial via Google, and it works. The issue is, that it also captures the commas inside those brackets, and I need it to ommit them. I don't know how to edit the regular expression to exclude captured commass from one of these bracket pairs. I tried something like ",(?=([^\"\\{\\[]*\"[^\"\\}\\]]*\")*[^\"]*$)", but it doesn't work. It messes it up even more. The same also applies to the colon separation, which is used in separation of key and value of a JSON object.
Is there a way to combine the "", {} and [] pairs together in the regex in such way that it works? Sorry if I look like a lame, but I really can't figure out how the regex should look like.
BTW, this is a code snippet I want to use it in:
public class JavaJSON {
private HashMap<String, Object> content;
// Constructors
/**
* Create new empty JSON object
*/
public JavaJSON() {
this.content = new HashMap<>();
}
// ...
/**
* Parse a JSON string to a JSON object
*
* #param JSON JSON string to be converted to JSON object
* #return JSON object from given string
*/
public static JavaJSON parse(#NotNull String JSON) {
if (!JSON.startsWith("{") || !JSON.endsWith("}")) return null;
// If this is not a valid JSON string, return nothing.
JavaJSON output = new JavaJSON();
String content = JSON.substring(1, JSON.length() - 1);
if (content.length() == 0) return output; // if empty, return an empty JSON object
// Regex literals
String commaSeparated = ",(?=([^\"]*\"[^\"]*\")*[^\"]*$)"; // TODO: Change commaSeparated to capture any {} [] "" pair group
String colonSeparated = ":(?=([^\"]*\"[^\"]*\")*[^\"]*$)"; // TODO: Change colonSeparated to capture any {} [] "" pair group
String[] tokens = content.split(commaSeparated);
if (tokens.length == 0) return null;
// Don't know exactly if this is going to happen, but better be sure
for (String token : tokens) {
String rawToken = token.trim();
if (rawToken.length() == 0) return null;
// Omitted comma, extra comma, etc. = JSON error
String[] mapToken = rawToken.split(colonSeparated);
if (mapToken.length < 2 || mapToken.length > 2) return null;
// Expected format = {"foo": "bar"}; format isn't valid
String mapKey = mapToken[0].trim();
String mapValue = mapToken[1].trim();
if (!mapKey.startsWith("\"") || !mapKey.endsWith("\"")) return null;
// Key must be a string
String rawMapKey = mapKey.substring(1, mapKey.length() - 1); // get quote-less variant
if (rawMapKey.length() == 0) return null;
// Key must not be empty
// check errors
if (mapValue.startsWith("{") && !mapValue.endsWith("}")) return null;
// Not a valid JSON object
if (mapValue.startsWith("[") && !mapValue.endsWith("]")) return null;
// Not a valid JSON array
if (mapValue.startsWith("\"") && !mapValue.endsWith("\"")) return null;
// Not a valid string
// get value object
Object rawMapValue;
// parse value object
if (mapValue.startsWith("\"") && mapValue.endsWith("\"")) {
rawMapValue = mapValue.substring(1, mapValue.length() - 1);
} else if (mapValue.startsWith("{") && mapValue.endsWith("}")) {
rawMapValue = parse(mapValue);
} else if (mapValue.startsWith("[") && mapValue.endsWith("]")) {
rawMapValue = parseArray(mapValue);
} else {
try {
rawMapValue = Long.parseLong(mapValue);
} catch (Exception e) {
try {
rawMapValue = Double.parseDouble(mapValue);
} catch (Exception f) {
return null;
// Not a valid number
}
}
}
output.update(rawMapKey, rawMapValue);
}
return output;
}
// ...
}
Related
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 question on how would be the best way to get the information from a string but that has JSON format.
{
"internal_id":"1234",
"moreInformation":"Failed authentication for user."
}
In this case, I want to get the value of "internal_id" and I already did, with subtring, lastIndexOf and indexOf
public static String returnInternalCode(String json){
String internalCode = json.substring(json.lastIndexOf("\"internal_id\":\"") + "\"internal_id\":\"".length(), json.length() - 1);
if (json.lastIndexOf("\"internal_id\":\"") == -1) return null;
return internalCode.substring(0, internalCode.indexOf("\""));
}
I also tried several JSONs with order changes that don't have the data and it also worked. I leave the full class of tests I did:
public class Test {
public static void main(String[] args) {
// Original JSON
String json = "{\"internal_id\":\"999999\",\"moreInformation\":\"Failed authentication for user, 1 authentication attempt remaining.\"}";
// Other JSON order
String json2 = "{\"moreInformation\":\"Failed authentication for user. Invalid response.\", \"moreInformation2\":\"Failed authentication for user. \", \"internal_id\":\"45678\"}";
// JSON without the internal_id
String json3 = "{\"moreInformation\":\"Failed authentication for user. Invalid response.\"}";
// JSON without moreInformation
String json4 = "{\"internal_id\":\"999999\"}";
System.out.println("JSON: ".concat(json4));
System.out.println("internalId: " + returnInternalId(json4));
System.out.println("moreInformation: " + returnMoreInformation(json4));
}
public static String returnInternalId(String json){
String internalCode = json.substring(json.lastIndexOf("\"internal_id\":\"") + "\"internal_id\":\"".length(), json.length());
if (json.lastIndexOf("\"internal_id\":\"") == -1) return null;
return internalCode.substring(0, internalCode.indexOf("\""));
}
public static String returnMoreInformation(String json){
String moreInformation = (json.substring(json.lastIndexOf("\"moreInformation\":\"") + "\"moreInformation\":\"".length(), json.length()));
if (json.lastIndexOf("\"moreInformation\":\"") == -1) return null;
return moreInformation.substring(0, moreInformation.indexOf("\""));
}
}
I would like to know if there are better ways to do what I did, such as with StringBuilder or StringBuffer and also to find out which way uses less memory or is faster to run, how do I know that? How long does it take to execute a method?
Thank you very much!
You can extract the values this way; Using Simple-json library
JSONObject jobj = (JSONObject) parser.parse(yourJsonString); // Pass the Json formatted String
String internal_id = (String) jobj.get("internal_id"); // Extract the value from your key
System.out.println(internal_id); // 1234
I have a Java object that contains a few String variables. When creating a json message from a Java object if one of the String values is alpha numeric, then the conversion will return back a quoted value. Else the conversion will return back a numeric value.
Example:
Class User {
String userid , password;
}
if userid = "tom" and password = "123456" then the JSON conversion returns back
"userid":"tom" and "password":123456 (numeric)
It should actually return "password":"123456"
How can I achieve this? I am using the Java parser from json.org and below is a snippet of code that converts the Java object to Json.
final JSONObject jsonObject = XML.toJSONObject(writer.toString());
res = jsonObject.toString(4);
It's because of stringToValue method in JSONObject.
It tries to guess a type.
It's open source so you can change it if you want.
Just return string.
/**
* Try to convert a string into a number, boolean, or null. If the string
* can't be converted, return the string.
*
* #param string
* A String.
* #return A simple JSON value.
*/
public static Object stringToValue(String string) {
if (string.equals("")) {
return string;
}
if (string.equalsIgnoreCase("true")) {
return Boolean.TRUE;
}
if (string.equalsIgnoreCase("false")) {
return Boolean.FALSE;
}
if (string.equalsIgnoreCase("null")) {
return JSONObject.NULL;
}
/*
* If it might be a number, try converting it. If a number cannot be
* produced, then the value will just be a string.
*/
char initial = string.charAt(0);
if ((initial >= '0' && initial <= '9') || initial == '-') {
try {
if (string.indexOf('.') > -1 || string.indexOf('e') > -1
|| string.indexOf('E') > -1
|| "-0".equals(string)) {
Double d = Double.valueOf(string);
if (!d.isInfinite() && !d.isNaN()) {
return d;
}
} else {
Long myLong = new Long(string);
if (string.equals(myLong.toString())) {
if (myLong.longValue() == myLong.intValue()) {
return Integer.valueOf(myLong.intValue());
}
return myLong;
}
}
} catch (Exception ignore) {
}
}
return string;
}
You could use staxon library instead: its JsonXMLConfigBuilder lets you control the behavior during conversion (such as autoprimitive which lets you define how you want to handle primitive values). Here's the code:
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><userid>tom</userid><password>123456</password></root>";
ByteArrayOutputStream bao = new ByteArrayOutputStream();
JsonXMLConfig config = new JsonXMLConfigBuilder().autoArray(true).autoPrimitive(false).prettyPrint(true).build();
try {
XMLEventReader reader = XMLInputFactory.newInstance().createXMLEventReader(IOUtils.toInputStream(xml));
XMLEventWriter writer = new JsonXMLOutputFactory(config).createXMLEventWriter(bao);
writer.add(reader);
reader.close();
writer.close();
} finally {
bao.close();
}
String json = bao.toString();
JsonXMLConfigBuilder()...autoPrimitive(false) does the trick you are looking for: the number fields are kept as Strings.
With this code sample, you need to add Saxion + commons-io (just for IOUtils.toInputStream(xml)) :
<dependency>
<groupId>de.odysseus.staxon</groupId>
<artifactId>staxon</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4<version>
</dependency>
Some documentation on staxon:
github
dzone
1)I need to check if String contains a String characters what will be the corect way how to do it ?
2) Are some ways how to corectly transform String to number and then compare theese two number s? Like String = "House":1234 is equal to "House":1234 but no to "house":123
Priview:
String token ="123"; False
String token = "ā123"; or other characters True utc.
if(isChars(token)){
Long value = toLong(token);
}
THANKS!
//EDIT
public BigDecimal eval() {
Stack<BigDecimal> stack = new Stack<BigDecimal>();
for (String token : getRPN()) {
if (operators.containsKey(token)) {
BigDecimal v1 = stack.pop();
BigDecimal v2 = stack.pop();
stack.push(operators.get(token).eval(v2, v1));
} else if (variables.containsKey(token)) {
stack.push(variables.get(token).round(mc));
} else if (functions.containsKey(token.toUpperCase())) {
Function f = functions.get(token.toUpperCase());
ArrayList<BigDecimal> p = new ArrayList<BigDecimal>(f.getNumParams());
for (int i = 0; i < f.numParams; i++) {
p.add(0, stack.pop());
}
BigDecimal fResult = f.eval(p);
stack.push(fResult);
} else if (isDate(token)) {
Long date = null;
try {
date = SU.sdf.parse(token).getTime();
} catch (ParseException e) {/* IGNORE! */
}
// mylog.pl("LONG DATE : "+new BigDecimal(date, mc));
stack.push(new BigDecimal(date, mc));
}//TODO HERE
else if (isChar(token)){
Long cha = toLong(token);
stack.push(new BigDecimal(cha, mc));
//TODO ENDS HERE
}
else {
// mylog.pl("Token : "+ token);
stack.push(new BigDecimal(token, mc));
}
}
return stack.pop().stripTrailingZeros();
}
Another way for determing whether string contains any chars is nice class StringUtils from apache-commons-lang library.
It contains several methods for analyzing string's contents. It seems that in your case you can use StringUtils.isAlphanumeric(CharSequence cs) or negation of StringUtils.isNumeric(CharSequence cs)'s result.
What about second part of your question, so I do not see here necessety of extracting numbers from string. You can compare strings "House":1234 and "house":123 using standard String.equals() method.
Long l;
try{
l = Long.parseLong(token);
} catch(NumberFormatException e){
//contains non-numeric character(s)
}
As for "transforming varchar into Long" - that sounds rather impossible, we do not have universally accepted way of doing that, and you did not provide one. However if I guess correctly that what you want is the number within the string disregarding the characters - you want regular expressions. The code you want could look like:
if (!StringUtils.isNumeric(token)){
String stripped = token.replaceAll("\\D","");
Long l = Long.parseLong(stripped);
}
I have a String containing a URL. I want to get just one piece of data out of it: an int that should be showing up in the query string.
So if the url is:
http://domain.tld/page.html?iVar=123
I want to get "123" into an int.
What's the most elegant way you know to do this?
You could try matching just that parameter in the URL string:
public static Integer getIVarParamValue(String urlStr) {
Pattern p = Pattern.compile("iVar=(\\d+)");
Matcher m = p.matcher(urlStr);
if (m.find()) {
return Integer.parseInt(m.group(1));
}
return null;
}
It seems you want to obtain get parameters and parse them. I have this method here (got it from somewhere on SO, I guess):
public static Map<String, List<String>> getQueryParams(String url) {
try {
Map<String, List<String>> params = new HashMap<String, List<String>>();
String[] urlParts = url.split("\\?");
if (urlParts.length > 1) {
String query = urlParts[1];
for (String param : query.split("&")) {
String[] pair = param.split("=");
String key = URLDecoder.decode(pair[0], "UTF-8");
String value = "";
if (pair.length > 1) {
value = URLDecoder.decode(pair[1], "UTF-8");
}
List<String> values = params.get(key);
if (values == null) {
values = new ArrayList<String>();
params.put(key, values);
}
values.add(value);
}
}
return params;
} catch (UnsupportedEncodingException ex) {
throw new AssertionError(ex);
}
}
So:
String var = WebUtils.getQueryParams(url).get("iVar");
int intVar = Integer.parseInt(var);
You can use the URL class.
i.e.:
URL myUrl = new URL("http://domain.tld/page.html?iVar=123");
String query = myUrl.getQuery(); //this will return iVar=123
//at this point you can either parse it manually (i.e. use some of the regexp in the other suggestions, or use something like:
String[] parts = query.split();
String variable = parts[0];
String value = parts[1];
This will work only for this case though and won't work if you have additional params or no params.
There are a number of solution that will split it into a param map online, you can see some here.
If it's really as simple as you describe: There is only 1 int in your URL and all you want is that int, I'd go for a regular expression. If it is actually more complicated see the other answers.
Pattern p = Pattern.compile("[0-9]+");
Matcher m = p.matcher("http://domain.tld/page.html?iVar=123");
if (m.find())
System.out.println(m.group());
This also could do the work :
public static int getIntParam(HttpServletRequest request, String name, int defaultValue) {
String value = request.getParameter(name);
try {
if (value != null) {
return Integer.valueOf(value);
}
} catch (NumberFormatException e) {
}
return defaultValue;
}
Hope it helps!
If the query string part of the URL is always the same (so if it was always iVar) you could use urlAsString.indexOf("iVar=") to find iVar= and then knowing the number is after that, extract the number. That is admittedly not the least brittle approach.
But if you're looking for all the query strings then Bozho's answer is much better.