Related
I am using Gson 2.8.1+ (I can upgrade if needed).
If I have the JsonObject:
"config" : {
"option_one" : {
"option_two" : "result_one"
}
}
}
... how can I convert this efficiently to the form:
"config.option_one.option_two" : "result_one"
Simple example:
public static void main(String[] args) {
String str = """
{
"config" : {
"option_one" : {
"option_two" : "result_one"
}
}
}""";
var obj = JsonParser.parseString(str).getAsJsonObject();
System.out.println(flatten(obj)); // {"config.option_one.option_two":"result_one"}
}
public static JsonObject flatten(JsonObject toFlatten) {
var flattened = new JsonObject();
flatten0("", toFlatten, flattened);
return flattened;
}
private static void flatten0(String prefix, JsonObject toFlatten, JsonObject toMutate) {
for (var entry : toFlatten.entrySet()) {
var keyWithPrefix = prefix + entry.getKey();
if (entry.getValue() instanceof JsonObject child) {
flatten0(keyWithPrefix + ".", child, toMutate);
} else {
toMutate.add(keyWithPrefix, entry.getValue());
}
}
}
Algorithm
Simplest algorithm you can come up with is recursive folding. You first dive recursively to the bottom of a structure, then ask if there is only one element in the map(you have to parse json with some framework to get a Map<string, object> structure). If there is, you join the string of parent field with property and set value of parent to value of that property. Then you move up and repeat the process until you are at the root. Of course, if map has multiple fields, you will move on to the parent and try egan.
Gson does not have anything like that, but it provides enough capabilities to build it on top: you can walk JSON streams (JsonReader) and trees (JsonElement, but not wrapped into JsonReader) stack-based and stack-based/recursively accordingly (streams may save much).
I would create a generic tree-walking method to adapt it for further purposes.
public static void walk(final JsonElement jsonElement, final BiConsumer<? super Collection<?>, ? super JsonElement> consumer) {
final Deque<Object> parents = new ArrayDeque<>();
parents.push("$");
walk(jsonElement, consumer, parents);
}
private static void walk(final JsonElement jsonElement, final BiConsumer<? super Collection<?>, ? super JsonElement> consumer, final Deque<Object> path) {
if ( jsonElement.isJsonNull() ) {
consumer.accept(path, jsonElement);
} else if ( jsonElement.isJsonPrimitive() ) {
consumer.accept(path, jsonElement);
} else if ( jsonElement.isJsonObject() ) {
for ( final Map.Entry<String, JsonElement> e : jsonElement.getAsJsonObject().entrySet() ) {
path.addLast(e.getKey());
walk(e.getValue(), consumer, path);
path.removeLast();
}
} else if ( jsonElement.isJsonArray() ) {
int i = 0;
for ( final JsonElement e : jsonElement.getAsJsonArray() ) {
path.addLast(i++);
walk(e, consumer, path);
path.removeLast();
}
} else {
throw new AssertionError(jsonElement);
}
}
Note that the method above also supports arrays. The walk method is push-semantics-driven: it uses callbacks to provide the walk progress. Making it lazy by returning an iterator or a stream would probably be cheaper and get the pull semantics applied. Also, CharSequence view elements would probably save on creating many strings.
public static String toJsonPath(final Iterable<?> path) {
final StringBuilder stringBuilder = new StringBuilder();
final Iterator<?> iterator = path.iterator();
if ( iterator.hasNext() ) {
final Object next = iterator.next();
stringBuilder.append(next);
}
while ( iterator.hasNext() ) {
final Object next = iterator.next();
if ( next instanceof Number ) {
stringBuilder.append('[').append(next).append(']');
} else if ( next instanceof CharSequence ) {
stringBuilder.append('.').append(next);
} else {
throw new UnsupportedOperationException("Unsupported: " + next);
}
}
return stringBuilder.toString();
}
Test:
final JsonElement jsonElement = Streams.parse(jsonReader);
final Collection<String> paths = new ArrayList<>();
JsonPaths.walk(jsonElement, (path, element) -> paths.add(JsonPaths.toJsonPath(path)));
for ( final String path : paths ) {
System.out.println(path);
}
Assertions.assertIterableEquals(
ImmutableList.of(
"$.nothing",
"$.number",
"$.object.subobject.number",
"$.array[0].string",
"$.array[1].string",
"$.array[2][0][0][0]"
),
paths
);
I am having the following sample from a JSON file:
[
{
"0":
{
"File":"file1.java",
"Class":"com.ETransitionActionType",
"Method":"values",
"Annotation":"Not Found"
}
},
{
"1":
{
"File":"file2.java",
"Class":"com.ETransitionParams",
"Method":"values",
"Annotation":"Not Found"
}
},
{
"2":
{
"File":"file3.java",
"Class":"com.phloc.commons.id.IHasID",
"Method":"getID",
"Annotation":"Not Found"
}
},
{
"4":
{
"File":"file3.java",
"Class":"com.ExecuteTransitionActionHandler",
"Method":"createBadRequestResponse",
"Annotation":"Not Found"
}
},
{
"5":
{
"File":"file3.java",
"Class":"com.ExecuteTransitionActionHandler",
"Method":"extractParametersFromAction",
"Annotation":"Not Found"
}
}]
How can I restructure this file using java so that it looks like:
[{
"file1.java": {
"com.ETransitionActionType": {
"values": {
"Annotation": "Not Found"
}
}
}
},
{
"file2.java": {
"com.ETransitionParams": {
"values": {
"Annotation": "Not Found"
}
}
}
},
{
"file3.java": {
"com.phloc.commons.id.IHasID": {
"getID": {
"Annotation": "Not Found"
}
},
"com.ExecuteTransitionActionHandler": {
"getID": {
"Annotation": "Not Found"
},
"extractParametersFromAction": {
"Annotation": "Not Found"
}
}
}
}
]
i.e. Going through the JSON file, searching it, and wherever the "File" attribute has the same value("file3.java" for example), we list all the relevant classes and methods inside and the same applies for the "Class" attribute, if it has the same name, we list all the methods inside it(So it's like comparing and sorting the values for the "File" and "Class" attributes).
I started with JSON simple library and wrote like the code below, but don't know how to go further!
Object object = (JSONArray)parser.parse(new FileReader("rawOutput.json"));
JSONArray jsonArray = (JSONArray) object;
for(int i = 0; i < jsonArray.size(); i++) {
System.out.println(jsonArray.get(i));
JSONObject jsonObject = (JSONObject)jsonArray.get(i);
String c = jsonObject.get("" + i + "").toString();
}
Any ideas? Your help is really appreciated!!!
I wrote a code to do what do you need but first you have to add this library to your project if you don't have already org.json.zip library, because I didn't have a library for parsing Json texts so I used this library for formatting the Json data, and I'm sorry if you don't understand the code completely because your request isn't so easy as yourself know and I created three functions to get the result and although I wrote some comments to understand easily, this is the code:-
Edit
...
import org.json.*;
...
...
public static void main(String[] args) throws JSONException {
System.out.println(getFormattedJson("json text"));
}
private static String getFormattedJson(String text) throws JSONException{
JSONArray result = new JSONArray();
JSONArray jsonArray = null;
//get the json array
jsonArray = new JSONArray(text);
//loop through items in the array and insert them formatted to the result
for (int i = 0; i < jsonArray.length(); i++) {
//get object inside the number
JSONObject object = getJsonChild(jsonArray.getJSONObject(i));
//get these attributes
String file = object.getString("File");
String clas = object.getString("Class");
String meth = object.getString("Method");
String anno = object.getString("Annotation");
//create a custom type of the object's attributes
Map<String, String> map = new HashMap<>();
map.put("Annotation", anno);
Map<String, Object> map1 = new HashMap<>();
map1.put(meth, map);
Map<String, Object> map2 = new HashMap<>();
map2.put(clas, map1);
Map<String, Object> map3 = new HashMap<>();
map3.put(file, map2);
//loop through repeating values to also add them to one value as you expected
for (int j = jsonArray.length() - 1; j > i; j--) {
JSONObject obj = getJsonChild(jsonArray.getJSONObject(j));
String file1 = obj.getString("File");
String clas1 = obj.getString("Class");
String meth1 = obj.getString("Method");
String anno1 = obj.getString("Annotation");
if (file1.equals(file)) {
if (map2.containsKey(clas1)) {
if (childrenContains(map2, meth1)) {
//if the difference was annotation value
map.put("Annotation", anno1);
} else {
//if the difference was method names
Map<String, String> map_ = new HashMap<>();
map_.put("Annotation", anno1);
((Map<String, Object>) map2.get(clas1)).put(meth1, map_);
}
} else {
//if the difference was class names
Map<String, String> map_ = new HashMap<>();
map_.put("Annotation", anno1);
Map<String, Object> map1_ = new HashMap<>();
map1_.put(meth1, map_);
map2.put(clas1, map1_);
}
//remove the (value added) object
jsonArray.remove(j);
}
}
//add the map to the result
result.put(map3);
}
return result.toString(4);
}
private static boolean childrenContains(Map<String, Object> map1, String meth1) {
for (String childKey : map1.keySet()) {
Map<String, Object> child = (Map<String, Object>) map1.get(childKey);
if (child.containsKey(meth1))
return true;
}
return false;
}
private static JSONObject getJsonChild(JSONObject object) throws JSONException {
Iterator<String> keys = object.keys();
String key = "";
while (keys.hasNext()) {
key = (String) keys.next();
}
return object.getJSONObject(key);
}
And the result for your sample using my code is:-
[
{"file1.java": {"com.ETransitionActionType": {"values": {"Annotation": "Not Found"}}}},
{"file2.java": {"com.ETransitionParams": {"values": {"Annotation": "Not Found"}}}},
{"file3.java": {
"com.ExecuteTransitionActionHandler": {
"createBadRequestResponse": {"Annotation": "Not Found"},
"extractParametersFromAction": {"Annotation": "Not Found"}
},
"com.phloc.commons.id.IHasID": {"getID": {"Annotation": "Not Found"}}
}}
]
And if you want to get the json data from a file so use the following function to create the JSONArray easily:-
private static JSONArray readFromFile(String filePath){
try {
BufferedReader br = new BufferedReader(new FileReader(filePath));
StringBuilder sb = new StringBuilder();
String line = br.readLine();
while (line != null) {
sb.append(line);
sb.append(System.lineSeparator());
line = br.readLine();
}
return new JSONArray(sb.toString());
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
And use it instead the text json data:-
...
//get the json array
jsonArray = readFromFile("FilePath");
...
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Foo {
public static void main(String... args) throws IOException {
String json = formatJson(new FileReader("rawOutput.json"));
System.out.println(json);
}
public static String formatJson(Reader reader) throws IOException {
// group array items by fileName
final Function<List<Map<String, Object>>, Map<String, List<Object>>> groupByFileName =
data -> data.stream().collect(Collectors.groupingBy(map -> (String)map.get("File"), TreeMap::new,
Collectors.mapping(Function.identity(), Collectors.toList())));
// convert source item structure into required
final Function<Map.Entry<String, List<Object>>, Map<String, Object>> convert = entry -> {
Map<String, Map<String, Map<String, String>>> tmp = new LinkedHashMap<>();
entry.getValue().stream()
.map(value -> (Map<String, String>)value)
.forEach(map -> {
Map<String, Map<String, String>> classes = tmp.computeIfAbsent(map.get("Class"), cls -> new TreeMap<>());
Map<String, String> methods = classes.computeIfAbsent(map.get("Method"), method -> new TreeMap<>());
map.entrySet().stream()
.filter(e -> !"Class".equals(e.getKey()) && !"Method".equals(e.getKey()) && !"File".equals(e.getKey()))
.forEach(e -> methods.put(e.getKey(), e.getValue()));
});
return Collections.singletonMap(entry.getKey(), tmp);
};
ObjectMapper mapper = new ObjectMapper();
// read json as array of Maps
List<Map<String, Object>> data = Arrays.stream(mapper.readValue(reader, Map[].class))
.map(map -> map.values().iterator().next())
.map(item -> (Map<String, Object>)item)
.collect(Collectors.toList());
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(groupByFileName.apply(data).entrySet().stream()
.map(convert).collect(Collectors.toList()));
}
}
You could create a map of maps to represent your grouping by "File" and "Class" for your list of (inner) JSON objects. It might look similar to
final Function<JSONObject, String> fileFunction = (JSONObject jsonObject) -> jsonObject.getString("File");
final Function<JSONObject, String> classFunction = (JSONObject jsonObject) -> jsonObject.getString("Class");
final Map<String, Map<String, List<JSONObject>>> groupedJsonObjects = jsonObjects.stream()
.collect(Collectors.groupingBy(fileFunction, Collectors.groupingBy(classFunction)));
I have variable of type java.util.Properties. I am trying to write it to a JSON file, and as well as read from that file.
The Properties variable looks something like below:
Properties inner3 = new Properties();
inner3.put("i1", 1);
inner3.put("i2", 100);
Properties inner2 = new Properties();
inner2.put("aStringProp", "aStringValue");
inner2.put("inner3", inner3);
Properties inner1 = new Properties();
inner1.put("aBoolProp", true);
inner1.put("inner2", inner2);
Properties topLevelProp = new Properties();
topLevelProp.put("count", 1000000);
topLevelProp.put("size", 1);
topLevelProp.put("inner1", inner1);
Naturally, when I serialize the topLevelProp to JSON I expect the result to be as below.
{
"inner1": {
"inner2": {
"aStringProp": "aStringValue",
"inner3": {
"i2": 100,
"i1": 1
}
},
"aBoolProp": true
},
"size": 1,
"count": 1000000
}
The above JSON result can be produced by using Gson in a pretty straight forward way, but when it is fed the same JSON string to desrialize, it fails.
Gson gson = new GsonBuilder().create();
String json = gson.toJson(topLevelProp); //{"inner1":{"inner2":{"aStringProp":"aStringValue","inner3":{"i2":100,"i1":1}},"aBoolProp":true},"size":1,"count":1000000}
//following line throws error: Expected a string but was BEGIN_OBJECT at line 1 column 12 path $.
Properties propObj = gson.fromJson(json, Properties.class);
Tried with Jackson as well:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true);
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
File file = new File("configs/config1.json");
mapper.writeValue(file, topLevelProp);
The last line throws error:
com.fasterxml.jackson.databind.JsonMappingException: java.util.Properties cannot be cast to java.lang.String (through reference chain: java.util.Properties["inner1"])
Tried to desrialize from the string as follows and it failed with the following error:
Properties jckProp = JsonSerializer.mapper.readValue(json, Properties.class);
Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: {"inner1":{"inner2":{"aStringProp":"aStringValue","inner3":{"i2":100,"i1":1}},"aBoolProp":true},"size":1,"count":1000000}; line: 1, column: 11] (through reference chain: java.util.Properties["inner1"])
How this can be handled?
Update: Following the idea of cricket_007, found com.fasterxml.jackson.databind.node.ObjectNode, can be used as follows:
ObjectNode jckProp = JsonSerializer.mapper.readValue(json, ObjectNode.class);
System.out.println(jckProp.get("size").asInt());
System.out.println("jckProp: " + jckProp);
System.out.println("jckProp.inner: " + jckProp.get("inner1"));
I think this can be the way forward for me, as I mostly have to read from JSON file.
The problem you have is that you are misusing java.util.Properties: it is NOT a multi-level tree structure, but a simple String-to-String map.
So while it is technically possibly to add non-String property values (partly since this class was added before Java generics, which made allowed better type safety), this should not be done. For nested structured, use java.util.Map or specific tree data structures.
As to Properties, javadocs say for example:
The Properties class represents a persistent set of properties.
The Properties can be saved to a stream or loaded from a stream.
Each key and its corresponding value in the property list is a string.
...
If the store or save method is called on a "compromised" Properties
object that contains a non-String key or value, the call will fail.
Now: if and when you have such "compromised" Properties instance, your best bet with Jackson or Gson is to construct a java.util.Map (or perhaps older Hashtable), and serialize it. That should work without issues.
As it was said above by StaxMan, you're misusing the Properties class and you're close about having heavy issues for using it like that due to lack of type information. However, you might also face the same case for weakly-typed maps. If it's a must for you, then you can use your custom Gson JsonDeserializer (note the JSON arrays issue):
final class PropertiesJsonDeserializer
implements JsonDeserializer<Properties> {
private static final JsonDeserializer<Properties> propertiesJsonDeserializer = new PropertiesJsonDeserializer();
private PropertiesJsonDeserializer() {
}
static JsonDeserializer<Properties> getPropertiesJsonDeserializer() {
return propertiesJsonDeserializer;
}
#Override
public Properties deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
final Properties properties = new Properties();
final JsonObject jsonObject = jsonElement.getAsJsonObject();
for ( final Entry<String, JsonElement> e : jsonObject.entrySet() ) {
properties.put(e.getKey(), parseValue(context, e.getValue()));
}
return properties;
}
private static Object parseValue(final JsonDeserializationContext context, final JsonElement valueElement) {
if ( valueElement instanceof JsonObject ) {
return context.deserialize(valueElement, Properties.class);
}
if ( valueElement instanceof JsonPrimitive ) {
final JsonPrimitive valuePrimitive = valueElement.getAsJsonPrimitive();
if ( valuePrimitive.isBoolean() ) {
return context.deserialize(valueElement, Boolean.class);
}
if ( valuePrimitive.isNumber() ) {
return context.deserialize(valueElement, Number.class); // depends on the JSON literal due to the lack of real number type info
}
if ( valuePrimitive.isString() ) {
return context.deserialize(valueElement, String.class);
}
throw new AssertionError();
}
if ( valueElement instanceof JsonArray ) {
throw new UnsupportedOperationException("Arrays are unsupported due to lack of type information (a generic list or a concrete type array?)");
}
if ( valueElement instanceof JsonNull ) {
throw new UnsupportedOperationException("Nulls cannot be deserialized");
}
throw new AssertionError("Must never happen");
}
}
Hence, it might be used like this:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(Properties.class, getPropertiesJsonDeserializer())
.create();
public static void main(final String... args) {
final Properties outgoingProperties = createProperties();
out.println(outgoingProperties);
final String json = gson.toJson(outgoingProperties);
out.println(json);
final Properties incomingProperties = gson.fromJson(json, Properties.class);
out.println(incomingProperties);
}
private static Properties createProperties() {
final Properties inner3 = new Properties();
inner3.put("i1", 1);
inner3.put("i2", 100);
final Properties inner2 = new Properties();
inner2.put("aStringProp", "aStringValue");
inner2.put("inner3", inner3);
final Properties inner1 = new Properties();
inner1.put("aBoolProp", true);
inner1.put("inner2", inner2);
final Properties topLevelProp = new Properties();
topLevelProp.put("count", 1000000);
topLevelProp.put("size", 1);
topLevelProp.put("inner1", inner1);
return topLevelProp;
}
with the following output:
{inner1={inner2={aStringProp=aStringValue, inner3={i2=100, i1=1}}, aBoolProp=true}, size=1, count=1000000}
{"inner1":{"inner2":{"aStringProp":"aStringValue","inner3": {"i2":100,"i1":1}},"aBoolProp":true},"size":1,"count":1000000}
{inner1={inner2={aStringProp=aStringValue, inner3={i2=100, i1=1}}, aBoolProp=true}, size=1, count=1000000}
Type info injection
You could save some type information though, if you inject the type information in the result JSON. Let's assume you are fine with storing numeric values as not primitives, but JSON objects having two keys like _$T and _$V to hold the actual type (a class indeed, not any java.reflect.Type, unfortunately) and the associated value respectively in order to restore the real type of the property. This can be applied to arrays either, but it's still not possible to hold a parameterized type due to the lack of type paremerization for instances that are parameterized somehow (unless you can reach it via a Class instance):
final class PropertiesJsonDeserializer
implements JsonDeserializer<Properties> {
private static final JsonDeserializer<Properties> propertiesJsonDeserializer = new PropertiesJsonDeserializer();
private PropertiesJsonDeserializer() {
}
static JsonDeserializer<Properties> getPropertiesJsonDeserializer() {
return propertiesJsonDeserializer;
}
#Override
public Properties deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
final Properties properties = new Properties();
final JsonObject jsonObject = jsonElement.getAsJsonObject();
for ( final Entry<String, JsonElement> e : jsonObject.entrySet() ) {
properties.put(e.getKey(), parseValue(context, e.getValue()));
}
return properties;
}
private static Object parseValue(final JsonDeserializationContext context, final JsonElement valueElement) {
if ( valueElement instanceof JsonObject ) {
return context.deserialize(valueElement, Properties.class);
}
if ( valueElement instanceof JsonPrimitive ) {
final JsonPrimitive valuePrimitive = valueElement.getAsJsonPrimitive();
if ( valuePrimitive.isBoolean() ) {
return context.deserialize(valueElement, Boolean.class);
}
if ( valuePrimitive.isNumber() ) {
return context.deserialize(valueElement, Number.class); // depends on the JSON literal due to the lack of real number type info
}
if ( valuePrimitive.isString() ) {
return context.deserialize(valueElement, String.class);
}
throw new AssertionError();
}
if ( valueElement instanceof JsonArray ) {
throw new UnsupportedOperationException("Arrays are unsupported due to lack of type information (a generic list or a concrete type array?)");
}
if ( valueElement instanceof JsonNull ) {
throw new UnsupportedOperationException("Nulls cannot be deserialized");
}
throw new AssertionError("Must never happen");
}
}
final class TypeAwarePropertiesSerializer
implements JsonSerializer<Properties> {
private static final JsonSerializer<Properties> typeAwarePropertiesSerializer = new TypeAwarePropertiesSerializer();
private TypeAwarePropertiesSerializer() {
}
static JsonSerializer<Properties> getTypeAwarePropertiesSerializer() {
return typeAwarePropertiesSerializer;
}
#Override
public JsonElement serialize(final Properties properties, final Type type, final JsonSerializationContext context) {
final JsonObject propertiesJson = new JsonObject();
for ( final Entry<Object, Object> entry : properties.entrySet() ) {
final String property = (String) entry.getKey();
final Object value = entry.getValue();
if ( value instanceof Boolean ) {
propertiesJson.addProperty(property, (Boolean) value);
} else if ( value instanceof Character ) {
propertiesJson.addProperty(property, (Character) value);
} else if ( value instanceof Number ) {
final JsonObject wrapperJson = newWrapperJson(value);
wrapperJson.addProperty("_$V", (Number) value);
propertiesJson.add(property, wrapperJson);
} else if ( value instanceof String ) {
propertiesJson.addProperty(property, (String) value);
} else if ( value instanceof Properties || value instanceof Collection || value instanceof Map ) {
propertiesJson.add(property, context.serialize(value));
} else if ( value != null ) {
final Class<?> aClass = value.getClass();
if ( aClass.isArray() ) {
final JsonObject wrapperJson = newWrapperJson(value);
wrapperJson.add("_$V", context.serialize(value));
propertiesJson.add(property, wrapperJson);
} else {
throw new UnsupportedOperationException("Cannot process: " + value);
}
} else /* now the value is always null, Properties cannot hold nulls */ {
throw new AssertionError("Must never happen");
}
}
return propertiesJson;
}
private static JsonObject newWrapperJson(final Object value) {
final JsonObject wrapperJson = new JsonObject();
wrapperJson.addProperty("_$T", value.getClass().getName());
return wrapperJson;
}
}
final class TypeAwarePropertiesDeserializer
implements JsonDeserializer<Properties> {
private static final JsonDeserializer<Properties> typeAwarePropertiesDeserializer = new TypeAwarePropertiesDeserializer();
private TypeAwarePropertiesDeserializer() {
}
static JsonDeserializer<Properties> getTypeAwarePropertiesDeserializer() {
return typeAwarePropertiesDeserializer;
}
#Override
public Properties deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
try {
final Properties properties = new Properties();
final JsonObject jsonObject = jsonElement.getAsJsonObject();
for ( final Entry<String, JsonElement> e : jsonObject.entrySet() ) {
properties.put(e.getKey(), parseValue(context, e.getValue()));
}
return properties;
} catch ( final ClassNotFoundException ex ) {
throw new JsonParseException(ex);
}
}
private static Object parseValue(final JsonDeserializationContext context, final JsonElement valueElement)
throws ClassNotFoundException {
if ( valueElement instanceof JsonObject ) {
final JsonObject valueObject = valueElement.getAsJsonObject();
if ( isWrapperJson(valueObject) ) {
return context.deserialize(getWrapperValueObject(valueObject), getWrapperClass(valueObject));
}
return context.deserialize(valueElement, Properties.class);
}
if ( valueElement instanceof JsonPrimitive ) {
final JsonPrimitive valuePrimitive = valueElement.getAsJsonPrimitive();
if ( valuePrimitive.isBoolean() ) {
return context.deserialize(valueElement, Boolean.class);
}
if ( valuePrimitive.isNumber() ) {
throw new AssertionError("Must never happen because of 'unboxing' above");
}
if ( valuePrimitive.isString() ) {
return context.deserialize(valueElement, String.class);
}
throw new AssertionError("Must never happen");
}
if ( valueElement instanceof JsonArray ) {
return context.deserialize(valueElement, Collection.class);
}
if ( valueElement instanceof JsonNull ) {
throw new UnsupportedOperationException("Nulls cannot be deserialized");
}
throw new AssertionError("Must never happen");
}
private static boolean isWrapperJson(final JsonObject valueObject) {
return valueObject.has("_$T") && valueObject.has("_$V");
}
private static Class<?> getWrapperClass(final JsonObject valueObject)
throws ClassNotFoundException {
return Class.forName(valueObject.get("_$T").getAsJsonPrimitive().getAsString());
}
private static JsonElement getWrapperValueObject(final JsonObject valueObject) {
return valueObject.get("_$V");
}
}
Now the topLevelProp can be filled also with:
topLevelProp.put("ARRAY", new String[]{ "foo", "bar" });
topLevelProp.put("RAW_LIST", asList("foo", "bar"));
if you have these special JSON deserializers applied:
private static final Gson typeAwareGson = new GsonBuilder()
.registerTypeAdapter(Properties.class, getTypeAwarePropertiesSerializer())
.registerTypeAdapter(Properties.class, getTypeAwarePropertiesDeserializer())
.create();
A sample output:
{RAW_LIST=[foo, bar], inner1={inner2={aStringProp=aStringValue, inner3={i2=100, i1=1}}, aBoolProp=true}, size=1, count=1000000, ARRAY=[Ljava.lang.String;#b81eda8}
{"RAW_LIST":["foo","bar"],"inner1":{"inner2":{"aStringProp":"aStringValue","inner3":{"i2":{"_$T":"java.lang.Integer","_$V":100},"i1":{"_$T":"java.lang.Integer","_$V":1}}},"aBoolProp":true},"size":{"_$T":"java.lang.Integer","_$V":1},"count":{"_$T":"java.lang.Integer","_$V":1000000},"ARRAY":{"_$T":"[Ljava.lang.String;","_$V":["foo","bar"]}}
{RAW_LIST=[foo, bar], inner1={inner2={aStringProp=aStringValue, inner3={i2=100, i1=1}}, aBoolProp=true}, size=1, count=1000000, ARRAY=[Ljava.lang.String;#e2144e4}
Summarizing up two approaches, you might want to eliminate the need of weak-typing and introduce explicit POJO mappings if possible.
Since I only needed a deserialization feature, i.e. generate Java properties for an incoming Json (in my case a REST endpoint), I quickly hacked this solution:
public class Configuration extends Properties {
public void load(JsonElement json) {
addJson("", json);
return;
}
public void addJson(String root, JsonElement json) {
// recursion for objects
if (json instanceof JsonObject) {
if (!root.equals("")) root += ".";
final JsonObject jsonObject = json.getAsJsonObject();
for ( final Entry<String, JsonElement> e : jsonObject.entrySet() ) {
addJson(root + e.getKey(), e.getValue());
}
return;
}
// recursion for arrays
if (json instanceof JsonArray) {
final JsonArray jsonArray = json.getAsJsonArray();
if (!root.equals("")) root += ".";
int count = 0;
for(final JsonElement e : jsonArray) {
addJson(root+count, e);
count++;
}
return;
}
// leaves: add property
this.setProperty(root, json.getAsString());
}
}
As you can see, this is extending the Properties class. Another option would of course be to initialize a Properties object beforehand and pass it into the recursion.
I hope this is useful to someone :-)
{
"files": {
"f1.png": {
"intext": "A",
"inval": 0,
"inbinary": false
},
"f2.png": {
"intext": "A",
"inval": 0,
"inbinary": true
}
}
}
How to access value of inval when the f1.png value is not fixed. i.e. the name of file can be anything, its not known so how can I access value for inval field for various files in this JSON using Java?
Please try below code,
import org.json.JSONException;
import org.json.JSONObject;
public static void main(String[] args) {
String jsonString = "{\"files\": {\"f1.png\": {\"intext\": \"A\",\"inval\": 0,\"inbinary\": false}, \"f2.png\": {\"intext\": \"A\",\"inval\": 0,\"inbinary\": true}}}";
try {
JSONObject jsonObject =new JSONObject(jsonString);
JSONObject jsonChildObject = (JSONObject)jsonObject.get("files");
Iterator iterator = jsonChildObject.keys();
String key = null;
while(iterator.hasNext()){
key = (String)iterator.next();
System.out.println("inval value: "+((JSONObject)jsonChildObject.get(key)).get("inval"));
}
}
catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Hope it solves your issue
Using Jackson and JsonNode, you'd do:
private static final ObjectReader READER = new ObjectMapper()
.getReader;
// blah
// read the node
final JsonNode node = READER.readTree(fromWhatever);
// access the inner "files" member
final JsonNode filesNode = node.get("files");
to access the inner object.
Then to walk the filesNode object you'd do:
final Iterator<Map.Entry<String, JsonNode>> iterator = filesNode.fields();
Map.Entry<String, JsonNode> entry;
while (iterator.hasNext()) {
entry = iterator.next();
// the "inval" field is entry.getValue().get("inval")
}
If you can use this project this becomes more simple:
// or .fromFile(), .fromReader(), others
final JsonNode node = JsonLoader.fromString(whatever);
final Map<String, JsonNode> map = JacksonUtils.nodeToMap(node.get("files"));
// walk the map
You can use JsonPath library to access child elements.
https://github.com/json-path/JsonPath
It can be as simple as
List<String> names = JsonPath.read(json, "$.files.*);
With some modifications.
You can use ObjectMapper.
First create a class Image.
import lombok.Data;
#Data
public class Image {
private String intext;
private Integer inval;
private Boolean inbinary;
}
and convert to Map
final ObjectMapper objectMapper = new ObjectMapper();
final String jsonString =
"{\"files\": {\"f1.png\": {\"intext\": \"A\",\"inval\": 0,\"inbinary\": false}, \"f2.png\": {\"intext\": \"A\",\"inval\": 0,\"inbinary\": true}}}";
final Map<String, Map<String, Image>> output =
objectMapper.readValue(
jsonString, new TypeReference<Map<String, Map<String, Image>>>() {});
Thanks.
for nested array type,
{
"combo": [
{"field":"divisions"},
{"field":"lob"}
]
}
below code will help.
Iterator<?> keys = jsnObj.keys();
while(keys.hasNext()) {
String key = (String) keys.next();
JSONArray list = (JSONArray) jsnObj.get(key);
if(list==null || list.length()==0)
return null;
List<String> comboList = new ArrayList<String>();
for(int i=0;i<list.length();i++){
JSONObject jsonObject = (JSONObject)list.get(i);
if(jsonObject==null)
continue;
comboList.add((String)jsonObject.get("fields"));
}
}
I created a method "Json to HashTable" and vice versa. I use HashTable because "Java" there are no associative arrays. My problem now is when there is an array in the json. This means from "Java" an array of HashTable :/ does not work at all but I think the solution is to use "List >" ...
I see this somewhat complicated. Any help? Is that hard or I complicate too?
Json example:
{"Config":[{"Name":"method1","Uses":"0","Event":["Start","Play"],"Action":{"Class":"Ads","Options":{"Class":"Webview","Url":"http:\/\/test.com\/action.php","Time":"10"}}},{"Name":"method2","Uses":"12","Event":["Loading"],"MaxTimes":"5","Options":{"Class":"Ads"}}]}
View in: http://json.parser.online.fr/
My code:
public Hashtable<?, ?> JSonDecode(String data) {
Hashtable<String, Object> htJS = new Hashtable<String, Object>();
try {
JSONObject objJS = new JSONObject(data);
Iterator<String> it = objJS.keys();
String key = null;
Object value = null;
while (it.hasNext()) {
key = it.next();
value = objJS.get(key);
if (value instanceof JSONObject) {
value = JSonObjectToHashtable(value.toString());
}
if (value instanceof JSONArray) {
value = JSonArrayToHashtable(value.toString());
}
htJS.put((String) key, value);
}
} catch (JSONException e) {
e.printStackTrace();
// No valid json
return null;
}
return htJS;
}
public Hashtable<?, ?> JSonObjectToHashtable(String data) {
Hashtable<String, Object> htJS = new Hashtable<String, Object>();
JSONObject objJS;
try {
objJS = new JSONObject(data);
Iterator<String> it = objJS.keys();
String key = null;
Object value = null;
while (it.hasNext()) {
key = it.next();
value = objJS.get(key);
if (value instanceof JSONObject) {
value = JSonObjectToHashtable(value.toString());
}
htJS.put((String) key, value);
}
} catch (JSONException e) {
e.printStackTrace();
}
return htJS;
}
public List<Map<String, Object>> JSonArrayToHashtable(String data) {
List<Map<String, Object>> listMap = new ArrayList<Map<String,Object>>();
Map<String,Object> entry = new HashMap<String,Object>();
JSONArray objJSA;
try {
objJSA = new JSONArray(data);
for (int i = 0; i < objJSA.length(); i++) {
JSONObject objJS = objJSA.getJSONObject(i);
Iterator<String> it = objJS.keys();
String key = null;
Object value = null;
while (it.hasNext()) {
key = it.next();
value = objJS.get(key);
if (value instanceof JSONObject) {
value = JSonObjectToHashtable(value.toString());
}
entry.put((String) key, value);
}
listMap.add(entry);
entry = new HashMap<String,Object>();
}
} catch (JSONException e) {
e.printStackTrace();
}
return listMap;
}
Map (Hashtable) API is similar to JSONObject API. There is really no need to convert JSONObject to Map unless your application uses Maps consistently.
If you need to convert JSONObject to Map, the Map can be of type Map<String, Object>, where Object can be one of the following types:
String
Primitive (Integer, Float, etc)
Map
Collection (Array, List, etc) of the tree types mentioned above