Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I need help extracting a JSON array string into an array of objects so that it can be later processed.
The JSON string is embedded as a value within a pipe delimited string that is itself an XML element value.
A sample string is as below
<MSG>registerProfile|.|D|D|B95||43|5000|43100||UBSROOT43|NA|BMP|508|{"biometrics":{"fingerprints":{"fingerprints":[{"position":"RIGHT_INDEX","image":{"format":"BMP","resolutionDpi":"508","data":"Qk12WQEAAAAAADYAAAA="}},{"position":"LEFT_INDEX","image":{"format":"BMP","resolutionDpi":"508","data":"Qk12WQEADoAAAA"}}]}}}</MSG>
How can I extract the JSON properties and store them in separate arrays like
Format[0] =BMP
Position[0] =RIGHT_INDEX
Data[0]=Qk12WQEAAAAAADYAAAA=
Format[1] =BMP
Position[1]=LEFT_INDEX
Data[1]= Qk12WQEADoAAAA
These objects would then be passed to a separate function like below
FingerprintImage(Format[0],Position[0],Data[0]);
// ...
FingerprintImage(Format[1],Position[1],Data[1]);
// ...
public FingerprintImage(String format, String position, String data) {
setFormat(format);
setPosition(position);
setData(data);
}
I am not a java developer, the following is hopefully helpful to yourself or others who can provide more succinct syntax in java.
Firstly, we should identify there different layers of data serialization going on with your value:
<MSG></MSG> This is an outer XML element, so the first step is to interpret this value as an XML fragment and extract the XML Value.
The reason that we use XML deserialization at this top level, and not just use the string position, is that the inner values may have been XML escaped, so we need to parse the inner value using the XML encoding rules.
This leaves us with the strimg value: registerProfile|.|D|D|B95||43|5000|43100||UBSROOT43|NA|BMP|508|{"biometrics":{"fingerprints":{"fingerprints":[{"position":"RIGHT_INDEX","image":{"format":"BMP","resolutionDpi":"508","data":"Qk12WQEAAAAAADYAAAA="}},{"position":"LEFT_INDEX","image":{"format":"BMP","resolutionDpi":"508","data":"Qk12WQEADoAAAA"}}]}}}
The next level is pipe-delimited, which is the same as CSV, except the escape character is a | and usually there is no other encoding rules, as | isn't considered part of the normal lexical domain and we shouldn't need any further escaping.
You could therefore split this string into an array.
The value we are interested in is the 15th element in the array, eithe you know this in advance, or you could simply iterate through the elements to find the first one that starts with {
This leaves a JSON value: {"biometrics":{"fingerprints":{"fingerprints":[{"position":"RIGHT_INDEX","image":{"format":"BMP","resolutionDpi":"508","data":"Qk12WQEAAAAAADYAAAA="}},{"position":"LEFT_INDEX","image":{"format":"BMP","resolutionDpi":"508","data":"Qk12WQEADoAAAA"}}]}}}
Now that we have isolated the inner value in JSON format, the usual thing to do next is deserialize this value into an object.
I know OP is asking for arrays, but we can realize JSON objects as arrays if we really want to with the right tools.
In C# the above process is pretty simple, I'm sure it should be in Java as well, but my attempts keep throwing errors.
So, lets instead assume (I know... Ass-U-Me...) that there is only ever a single JSON value in the pipe-delimited array, with this knoweldge we can isolate the JSON using int String.IndexOf(str)
String xml = "<MSG>registerProfile|.|D|D|B95||43|5000|43100||UBSROOT43|NA|BMP|508|{\"biometrics\":{\"fingerprints\":{\"fingerprints\":[{\"position\":\"RIGHT_INDEX\",\"image\":{\"format\":\"BMP\",\"resolutionDpi\":\"508\",\"data\":\"Qk12WQEAAAAAADYAAAA=\"}},{\"position\":\"LEFT_INDEX\",\"image\":{\"format\":\"BMP\",\"resolutionDpi\":\"508\",\"data\":\"Qk12WQEADoAAAA\"}}]}}}</MSG>";
int start = xml.indexOf('{');
int end = xml.lastIndexOf('}') + 1; // +1 because we want to include the last character, so we need the index after it
String json = xml.substring(start, end);
results in: {"biometrics":{"fingerprints":{"fingerprints":[{"position":"RIGHT_INDEX","image":{"format":"BMP","resolutionDpi":"508","data":"Qk12WQEAAAAAADYAAAA="}},{"position":"LEFT_INDEX","image":{"format":"BMP","resolutionDpi":"508","data":"Qk12WQEADoAAAA"}}]}}}
Formatted to be pretty:
{
"biometrics": {
"fingerprints": {
"fingerprints": [
{
"position": "RIGHT_INDEX",
"image": {
"format": "BMP",
"resolutionDpi": "508",
"data": "Qk12WQEAAAAAADYAAAA="
}
},
{
"position": "LEFT_INDEX",
"image": {
"format": "BMP",
"resolutionDpi": "508",
"data": "Qk12WQEADoAAAA"
}
}
]
}
}
}
One way would be to create a class structure that matches this JSON value, then we can simply .fromJson() for the whole value, instead, lets meet halfway so we only need to define the inner class structure for the data we will actually use.
Now from this structure we can see there is an outer object that only has a single property called biometrics, this value is again an object witha single property called fingerprints. The value of this property is another object that has a single property called fingerprints except that this time it has an array value.
The following is a proof in Java, I have included first an example using serialization (using the gson library) and after that a similar implementation using only the simple-JSON library to read the values in arrays.
Try it out on JDoodle.com
MyClass.java
import java.util.*;
import java.lang.*;
import java.io.*;
//import javax.json.*;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import com.google.gson.Gson;
public class MyClass {
public static void main(String args[]) {
String xml = "<MSG>registerProfile|.|D|D|B95||43|5000|43100||UBSROOT43|NA|BMP|508|{\"biometrics\":{\"fingerprints\":{\"fingerprints\":[{\"position\":\"RIGHT_INDEX\",\"image\":{\"format\":\"BMP\",\"resolutionDpi\":\"508\",\"data\":\"Qk12WQEAAAAAADYAAAA=\"}},{\"position\":\"LEFT_INDEX\",\"image\":{\"format\":\"BMP\",\"resolutionDpi\":\"508\",\"data\":\"Qk12WQEADoAAAA\"}}]}}}</MSG>";
int start = xml.indexOf('{');
int end = xml.lastIndexOf('}') + 1; // +1 because we want to include the last character, so we need the index after it
String jsonString = xml.substring(start, end);
JSONParser parser = new JSONParser();
Gson gson = new Gson();
try
{
// locate the fingerprints inner array using simple-JSON (org.apache.clerezza.ext:org.json.simple:0.4 )
JSONObject jsonRoot = (JSONObject) parser.parse(jsonString);
JSONObject biometrics = (JSONObject)jsonRoot.get("biometrics");
JSONObject fpOuter = (JSONObject)biometrics.get("fingerprints");
JSONArray fingerprints = (JSONArray)fpOuter.get("fingerprints");
// Using de-serialization from gson (com.google.code.gson:gson:2.8.6)
FingerPrint[] prints = new FingerPrint[fingerprints.size()];
for(int i = 0; i < fingerprints.size(); i ++)
{
JSONObject fpGeneric = (JSONObject)fingerprints.get(i);
prints[i] = gson.fromJson(fpGeneric.toString(), FingerPrint.class);
}
// Call the FingerprintImage function using the FingerPrint objects
System.out.print("FingerPrint Object Index: 0");
FingerprintImage(prints[0].image.format, prints[0].position, prints[0].image.data );
System.out.println();
System.out.print("FingerPrint Object Index: 1");
FingerprintImage(prints[1].image.format, prints[1].position, prints[1].image.data );
System.out.println();
// ALTERNATE Array Implementation (doesn't use gson)
String[] format = new String[fingerprints.size()];
String[] position = new String[fingerprints.size()];
String[] data = new String[fingerprints.size()];
for(int i = 0; i < fingerprints.size(); i ++)
{
JSONObject fpGeneric = (JSONObject)fingerprints.get(i);
position[i] = (String)fpGeneric.get("position");
JSONObject image = (JSONObject)fpGeneric.get("image");
format[i] = (String)image.get("format");
data[i] = (String)image.get("data");
}
System.out.print("Generic Arrays Index: 0");
FingerprintImage(format[0], position[0], data[0] );
System.out.println();
System.out.print("Generic Arrays Index: 1");
FingerprintImage(format[1], position[1], data[1] );
System.out.println();
}
catch (ParseException ignore) {
}
}
public static void FingerprintImage(String format, String position, String data) {
setFormat(format);
setPosition(position);
setData(data);
}
public static void setFormat(String format) {
System.out.print(", Format=" + format);
}
public static void setPosition(String position) {
System.out.print(", Position=" + position);
}
public static void setData(String data) {
System.out.print(", Data=" + data);
}
}
output
FingerPrint.java
public class FingerPrint {
public String position;
public FingerPrintImage image;
}
FingerPrintImage.java
public class FingerPrintImage {
public String format;
public int resolutionsDpi;
public String data;
}
Deserialization techniques are generally considered superior to forced/manual parsing especially when we need to pass around references to multiple parsed values. In the above example, by simply reading format, position and data into separate arrays, the relationship between them has become de-coupled, through our code implementation we can still use them together as long as we use the same array index, but the structure no longer defines the relationship between the values. De-serializing into a typed structure preserves the relationship between values and simplifies the task of passing around values that are related to each other.
update
If you used serialization, then you could pass through the equivalent FingerPrint object to any methods that need it, instead of passing through the related values individually, further to this you could simply pass around the entire array of FingerPrint objects.
public static void FingerprintImage(FingerPrint print) {
setFormat(print.image.format);
setPosition(print.position);
setData(print.image.data);
}
To process multiple FingerPrint objects in a batch, change the method to accept an array: FingerPrint[]
You could use the same technique to process arrays or each of the Format, Postion and Data, though it is really poor practise to do so. Passing around multiple arrays and expecting the receiving code to know that each of the arrays is supposed to be interpreted in sync, that is the same index in each array corresponds to the same finger print, this level of implementation detail is too ambiguous and will lead to maintenance nightmares down the track, its far better to learn and become proficient in OO concepts and creating business objects for passing around related data elements, instead of packaging everything into disassociated arrays.
The following code can assist you in processing multiple items using OPs array method but it should highlight why the practise is a bad habit to pickup:
public static void FingerprintImage(String[] formats, String[] positions, String[] datas) {
// now you must iterate each of the arrays using the same index
// however as there are no restrictions on the arrays, for each array
// and each index we should be checking that the array has not gone out
// of length.
}
From an OO point of view, passing through multiple arrays like this raises a number of issues, firstly, the developer will simply need to know that the same index must be used in each array to retrieve correlated information.
The next important issue is error handling...
If datas only has 1 element, but positions has 2 elements, which of the 2 elements does the 1 data element belong to? Or does this indicate that the same data should be used for both?
There are many other issues, consider when you expect 3 elements...
While you can get away with what seems like a shortcut in code if you really need to, you really shouldn't unless you absolutely understand what you are doing, you fully document the related code and you are taking responsibility for the potential fall out down the track.
Related
I have two objects that are being stored in arrays:
Game(String creator, String title, int releaseYear, int NumberSold)
Creator(String name, String gamesWorkedOn)
Game(creator) has multiple creators, so is stored as a string like this: "creator1, creator2, creator3" using commas to separate their values.
Not all games have multiple creators and there are not many different creators in total.
What I am trying to do is loop through an array of Game(games) and extract a creator variable from it and assign it to the Creator(name) and then match any games that creator is mentioned in and assign those title variables to Creator(gamesWorkedOn).
So far I have this:
public static void PopulateCreators(ArrayList<Game> games) {
//populating an array of Creators with games they have worked on
boolean match = false;
String thisCreator;
String gamesWorkedOn;
ArrayList<Creator> creatorArray = new ArrayList<Creator>();
for (int i = 0; i < games.size(); i++) {
thisCreator = games.get(i).getGameCreator();
thisCreator = thisCreator.replaceAll(", ", "\n");
Which gives me this output using a sysout:
Shigeru Miyamoto
Satoshi Tajiri
Yoshiaki Koizumi
Koichi Hayashida
Shigeru Miyamoto
My desired output would be to have something like this:
name = "Shigeru Miyamoto"
gamesWorkedOn = "game1, game2, game3"
I am looking at using a for loop but am unsure on how to implement it here.
Edit:
I forgot to mention a couple of details that I didn't think were important but I will be a bit clearer now. This is a Swing based project I am working on that takes user inputs and stores these arrays which are then saved into a JSON file that is read upon loading of the application and when a user clicks a 'save' button.
What you seem to want to do is map the creators to all the games that they have created or helped create. I'm going to start by creating a simplified version of the problem.
You have a list of:
class Game {
Set<Creator> creators;
}
which you want to convert to:
Map<Creator, Set<Game>> createdGames; // Map of creator name to games created
The first thing to do here is to find all of the unique creators to start adding to the map. This can be done with the stream API.
createdGames = gameList.stream().flatMap(game -> game.creators.stream()).distinct().collect(Collectors.toMap(Function.identity(), v -> new HashSet<>()));
Now you can just loop through all the games again and add the game to a creator's set if they took part in the creation of that game.
for(Game game : gameList) {
for(Creator creator : createdGames.keySet()) {
if(game.creators.contains(creator)) {
createdGames.get(creator).add(game);
}
}
}
I'm trying to make sure my Jersey request parameters are sanitized.
When processing a Jersey GET request, do I need to filter non String types?
For example, if the parameter submitted is an integer are both option 1 (getIntData) and option 2 (getStringData) hacker safe? What about a JSON PUT request, is my ESAPI implementation enough, or do I need to validate each data parameter after it is mapped? Could it be validated before it is mapped?
Jersey Rest Example Class:
public class RestExample {
//Option 1 Submit data as an Integer
//Jersey throws an internal server error if the type is not Integer
//Is that a valid way to validate the data?
//Integer Data, not filtered
#Path("/data/int/{data}/")
#GET
#Produces(MediaType.TEXT_HTML)
public Response getIntData(#PathParam("data") Integer data){
return Response.ok("You entered:" + data).build();
}
//Option 2 Submit data as a String, then validate it and cast it to an Integer
//String Data, filtered
#Path("/data/string/{data}/")
#GET
#Produces(MediaType.TEXT_HTML)
public Response getStringData(#PathParam("data") String data) {
data = ESAPI.encoder().canonicalize(data);
if (ESAPI.validator().isValidInteger("data", data, 0, 999999, false))
{
int intData = Integer.parseInt(data);
return Response.ok("You entered:" + intData).build();
}
return Response.status(404).entity("404 Not Found").build();
}
//JSON data, HTML encoded
#Path("/post/{requestid}")
#POST
#Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON})
#Produces(MediaType.TEXT_HTML)
public Response postData(String json) {
json = ESAPI.encoder().canonicalize(json);
json = ESAPI.encoder().encodeForHTML(json);
//Is there a way to iterate through each JSON KeyValue and filter here?
ObjectMapper mapper = new ObjectMapper();
DataMap dm = new DataMap();
try {
dm = mapper.readValue(json, DataMap.class);
} catch (Exception e) {
e.printStackTrace();
}
//Do we need to validate each DataMap object value and is there a dynamic way to do it?
if (ESAPI.validator().isValidInput("strData", dm.strData, "HTTPParameterValue", 25, false, true))
{
//Is Integer validation needed or will the thrown exception be good enough?
return Response.ok("You entered:" + dm.strData + " and " + dm.intData).build();
}
return Response.status(404).entity("404 Not Found").build();
}
}
Data Map Class:
public class DataMap {
public DataMap(){}
String strData;
Integer intData;
}
The short answer is yes, though by "filter" I interpret it as "validate," because no amount of "filtering" will EVER provide you with SAFE data. You can still run into integer overflows in Java, and while those may not have immediate security concerns, they could still put parts of your application in an unplanned for state, and hacking is all about perturbing the system in ways you can control.
You packed waaaaay too many questions into one "question," but here we go:
First off, the lines
json = ESAPI.encoder().canonicalize(json);
json = ESAPI.encoder().encodeForHTML(json);
Aren't doing what you think they're doing. If your JSON is coming in as a raw String right here, these two calls are going to be applying mass rules across the entire string, when you really need to handle these with more surgical precision, which you seem to at least be subconsciously aware of in the next question.
//Is there a way to iterate through each JSON KeyValue and filter
here?
Partial duplicate of this question.
While you're in the loop discussed here, you can perform any data transformations you want, but what you should really be considering is using the JSONObject class referenced in that first link. Then you'll have JSON parsed into an object where you'll have better access to JSON key/value pairs.
//Do we need to validate each DataMap object value and is there a
dynamic way to do it?
Yes, we validate everything that comes from a user. All users are assumed to be trained hackers, and smarter than you. However if you handled filtering before you do your data mapping transformation, you don't need to do it a second time. Doing it dynamically?
Something like:
JSONObject json = new JSONObject(s);
Iterator iterator = json.keys();
while( iterator.hasNext() ){
String data = iterator.next();
//filter and or business logic
}
^^That syntax is skipping typechecks but it should get you where you need to go.
/Is Integer validation needed or will the thrown exception be good
enough?
I don't see where you're throwing an exception with these lines of code:
if (ESAPI.validator().isValidInput("strData", dm.strData, "HTTPParameterValue", 25, false, true))
{
//Is Integer validation needed or will the thrown exception be good enough?
return Response.ok("You entered:" + dm.strData + " and " + dm.intData).build();
}
Firstly, in java we have autoboxing which means this:
int foo = 555555;
String bar = "";
//the code
foo + bar;
Will be cast to a string in any instance. The compiler will promote the int to an Integer and then silently call the Integer.toString() method. Also, in your Response.ok( String ); call, THIS is where you're going to want to encodeForHTML or whatever the output context may be. Encoding methods are ALWAYS For outputting data to user, whereas canonicalize you want to call when receiving data. Finally, in this segment of code we also have an error where you're assuming that you're dealing with an HTTPParameter. NOT at this point in the code. You'll validate http Parameters in instances where you're calling request.getParameter("id"): where id isn't a large blob of data like an entire JSON response or an entire XML response. At this point you should be validating for things like "SafeString"
Usually there are parsing libraries in Java that can at least get you to the level of Java objects, but on the validation side you're always going to be running through every item and punting whatever might be malicious.
As a final note, while coding, keep these principles in mind your code will be cleaner and your thought process much more focused:
user input is NEVER safe. (Yes, even if you've run it through an XSS filter.)
Use validate and canonicalize methods whenever RECEIVING data, and encode methods whenever transferring data to a different context, where context is defined as "Html field. Http attribute. Javascript input, etc...)
Instead of using the method isValidInput() I'd suggest using getValidInput() because it will call canonicalize for you, making you have to provide one less call.
Encode ANY time your data is going to be passed to another dynamic language, like SQL, groovy, Perl, or javascript.
I'm stuck on this program I'm making for school. Here's my code:
public static void experiencePointFileWriter() throws IOException{
File writeFileResults = new File("User Highscore.txt");
BufferedWriter bw;
bw = new BufferedWriter(new FileWriter(writeFileResults, true));
bw.append(userName + ": " + experiencePoints);
bw.newLine();
bw.flush();
bw.close();
FileReader fileReader = new FileReader(writeFileResults);
char[] a = new char[50];
fileReader.read(a); // reads the content to the array
for (char c : a)
System.out.print(c); // prints the characters one by one
fileReader.close();
}
The dilemma I'm facing is how can I sort new scores with the scores in writeFileResults by the numerical value of int experiencePoints? If you're wondering about the variables userName is assigned by a textfield.getText method, and an event happens when you press one of 36 buttons which launches a math.Random statement with one of 24 possible outcomes. They all add different integer numbers to experiencePoints.
Well, I don't want to do your homework, and this does seem introductory so I'd like to give you some hints.
First, there's a few things missing:
We don't have some of the variables you've given us, so there is no type associated with oldScores
There is no reference to userName or experiencePoints outside this method call
If you can add this information, it would make this process easier. I could infer things, but then I might be wrong, or worse yet, have you learn nothing because I did your assignment for you. ;)
EDIT:
So, based on extra information, you're data file is holding an "array" of usernames and experience values. Thus, the best way (read: best design, not shortest) would be to load these into custom objects then write a comparator function (read: implement the abstract class Comparator).
Thus, in pseudo-Java, you'd have:
Declare your data type:
private static class UserScore {
private final String name;
private final double experience;
// ... fill in the rest, it's just a data struct
}
In your reader, when you read the values, split each line to get the values, and create a new List<UserScore> object which contains all of the values read from the file (I'll let you figure this part out)
After you have your list, you can use Collections#sort to sort the list to be the correct order, here would be an example of this:
// assuming we have our list, userList
Collections.sort(userList, new Comparator<UserScore>() {
public int compare(UserScore left, UserScore right) {
return (int)(left.getExperience() - right.getExperience()); // check the docs to see why this makes sense for the compare function
}
}
// userList is now sorted based on the experience points
Re-write your file, as you see fit. You now have a sorted list.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
We are working on a game and looking to develop a functionality which will allow us to mix various items in a manner similar to "alchemy" game. The main idea is that we have a number of elements, which can be split into three groups: basic, intermediate and final. Basic resources can be merged together and make an intermediate resource, intermediate resources can be merged with intermediate and basic resources and make final and so on.
So, we are thinking about having 2 HashMaps: one would have a indicate what each resource is combinable with, second one would map what each resource would be made of. Is there a better way to do this? Any data structure that we are not aware of?
Thanks
Just write your own Datastructure like this
public class Element {
enum Type{BASIC, INTERMEDIATE, FINAL};
private Type type;
private String name;
private List<Element> combinable;
}
What you want is an enum containing all your elements, with a couple of methods. Here is an example, feel free to use it if it suites your needs.
If desired, you can also make a second enum for Type (as Templar suggested) and add it as a field in you Element enum.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public enum Element {
//Example instances, replace with what is appropriate for your game.
WATER, // Basic
WOOD, // Basic
IRON, // Basic
STONE, // Basic
FIRE, // Basic
CARBON(WOOD, FIRE), //Intermediate
FORGE(STONE, IRON), // Intermediate
STEEL(FORGE, IRON); // Final
private Element[] parts;
private Element() {
//instantiates parts to prevent NullPointerException
this.parts = new Element[0];
}
private Element(Element... parts) {
this.parts = parts;
}
/**
* return all the parts of this Element.
* #return
*/
public List<Element> getParts() {
return Arrays.asList(parts);
}
/**
* find all elements that have this Element listed as one of their parts.
*
* #param part
* #return
*/
public List<Element> getComposites() {
List<Element> composites = new ArrayList<Element>();
// Iterate through all Elements
for (Element composite : Element.values()) {
// Iterate through each Element's parts
for (Element part : composite.parts) {
// If the element has a part equal to the argument,
// Add the element to the list of composites.
if (part == this) {
composites.add(composite);
}
}
}
return composites;
}
}
You should use the Composite design pattern.
In your case BasicResource is a leaf class.
intermediate and final are composites.
I would actually separate elements and their combinability - if each element has to contain a list of elements it's combinable with, whenever you want to add a new element you have to go back and add it to all old elements you want it to combine with.
I'd separate the concept out into something like "Elements" and "Formulas" -
class Element {
enum Type{NULL, BASIC, INTERMEDIATE, FINAL};
private Type type;
private String name;
// ....
}
class Formula {
private List<Element> requires = new ArrayList<Element>();
private Element produces;
public Formula(List<Element> requires, Element produces) {
Collections.copy(requires, this.requires);
this.produces = produces;
}
public final List<Element> requiredElements() {
return Collections.unmodifiableList(requires);
}
public final boolean applyFormula(List<Element> ingredients) {
for (Element e : requires) {
if (!ingredients.contains(e)) {
// ingredients doesn't contain a required element - return early.
return false;
}
}
for (Element e : requires) {
ingredients.remove(e);
}
ingredients.add(produces);
return true;
}
}
If you're creating a game, having this data hard-coded in your Java source code is going to make things a pain. You'll have to recompile every time you want to add a new element, change a relationship (what is composed of what), etc.
Instead, I'd recommend storing all of your element information in an external source and then reading it in / accessing it from your program. You could do this with a database, but I have a feeling that's a little bit overkill (at least for now). Instead, you could use a nice readable, plain-text, standardized format like JSON to define your elements and their relationships externally, and then import all the data using a library (I'd suggest GSon) for easy access in your program.
As for the data structure, I think your choice of HashMaps would work just fine. Since JSON is built on two basic types of data structures—lists [] and maps {}—that's what Gson would convert things to anyway. Here's a very simple example of what how I'd envision your element specification:
{
"elements" : {
"iron" : "basic",
"carbon" : "basic",
"steel" : "intermediate"
},
"formulae" : {
"steel" : [ "iron", "carbon" ]
}
}
You could read that in with Gson (or whatever JSON library you choose), and then build whatever other data structures you need from that. If you can figure out how to get Gson to create the data structures you want directly (I know this is possible, but I don't remember how hard it is to do the configuration), then that would be even better. For example, if you could turn the "formulae" value into a BidiMap (the Apache Commons bi-directional map) then that might be very useful (but you'd also need to turn the components list into a set to keep it order-agnostic, e.g. iron+carbon is the same as carbon+iron).
For even more dynamic behavior, you could add a feature into your program to allow you to reload all of your elements data while your game is still running! (This might make debugging easier.)
I know this isn't exactly what you were asking, but I hope you find my suggestions helpful anyway!
I would like to store a string[][]-array in a database as datatype "blob". I just found out that I have to convert it, otherwise the data would be practically lost.
What would be the best way to do that?
I suppose I should serialize and de-serialize the array, unfortunately I am not quite experienced in that area. So any help would be appreciated.
PS: I guess I should have said that I need to do that on Android, thus using SQLite. The string[][] has no fixed number of rows or columns.
Why a blob? Why not a clob? Or better yet, why not a varchar? Depending on what you're going to do with the data, you should store the data as xml or json in a varchar column. It would be searchable too, if necessary.
You didn't say what's in your array but possibly another table would fit the bill, though determining that is far outside the scope of this question (it would make a good new question though).
No, far better to serialize your array as text and store it as such.
Edit... A library like JSON-lib supports bi-directional serialization on multidimensional arrays. Just run your array through JSON-lib to get a JSON string, store that string, then when you want your array back run the string through JSON-lib.
I prefer my text to be in the database as text so I can search for it and view it with one of the many database tools available. I don't want to run code to see what's in a column, and if I need to tweak a value by hand during development I want to be able to update the value, not run a program to do it for me.
Okay, assuming there won't be nulls. Write out:
The number of "rows" as a fixed 4-byte value
For each "row":
The number of "columns" as a fixed 4-byte value
For each string:
Convert the string in bytes, e.g. in UTF-8 (text.getBytes("UTF-8"))
Write out the number of bytes as a fixed 4-byte value
Write out the data for the string
You could just use DataOutputStream's writeUTF method for the last part, but that would make it slightly harder to read from non-Java platforms. It depends on your requirements. Using DataOutputStream would make it easier to handle in general though. For example:
private static final Charset UTF8 = Charset.forName("UTF-8");
private static byte[] SerializeText(String[][] array)
{
int rows = array.length;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(rows);
for (int i = 0; i < rows; i++)
{
int columns = array[i].length;
dos.writeInt(columns);
for (int j = 0; j < columns; j++)
{
byte[] utf8 = array[i][j].getBytes(UTF8);
dos.writeInt(utf8.length);
dos.write(utf8, 0, utf8.length);
}
}
dos.flush(); // May not be necessary
return baos.toByteArray();
}
Instead of XML, I just discovered a JSON library (thanks to this question), named google gson.
You just have to add the .jar to your classpath, and I give you the code for serialization and deserialization:
import com.google.gson.Gson;
public class JsonTest {
public static void main(String[] args) {
String[][] fruits = { { "Banana", "Apple", "Blueberry" }, { "Cherry" }, { "Lemon", "Mango" } };
Gson gson = new Gson();
// Serialization
String json = gson.toJson(fruits);
// Print: [["Banana","Apple","Blueberry"],["Cherry"],["Lemon","Mango"]]
System.out.println(json);
// Deserialization
String[][] result = gson.fromJson(json, String[][].class);
}
}
I am really happy that I found this library, XML was too much verbose.
(Sorry for spelling mistakes, I am French.)
You can store it as xml with the xstream library.
It is not very effective due to xml tags, but it works well and it is easy to use:
String[][] strs = {
{ "row1_col1", "row1_col2", "row1_col3" },
{ "row2_col1" },
{ "row3_col1", "row3_col2" }
};
XStream xstream = new XStream();
xstream.alias("saa", String[][].class);
xstream.alias("sa", String[].class);
xstream.alias("s", String.class);
String xml = xstream.toXML(str);
System.out.println(xml);
The result:
<saa>
<sa>
<s>row1_col1</s>
<s>row1_col2</s>
<s>row1_col3</s>
</sa>
<sa>
<s>row2_col1</s>
</sa>
<sa>
<s>row3_col1</s>
<s>row3_col2</s>
</sa>
</saa>
Deserialize:
String[][] strs = (String[][])xstream.fromXML(xml);
I found a solution that works for me.
The main problem was that apparently the BLOB has not been saved properly in the database. I used the Android convenience methods to update the database, and now it works even better than I first anticipated.