I use reasteasy to call REST API.
My problem is that the JSON from the REST API has dynamic values. For example:
"labels": {
"kubernetes.io/hostname": "192.168.200.176",
"node": "master"
}
Where "node" and "kubernetes.io/hostname" could be any string.
I've tried to map "labels" into a Map <String, String> object. The variable is correctly created but it remains empty. How can I generate a dictionary with for example {"kubernetes.io/hostname": "192.168.200.176", "node": "master"}?`
If you're fine with just creating a Map instead of a specific domain object, you can simply parse the JSON yourself to get a list of the keys and create the Map yourself.
Here's an example using org.json:
import java.util.HashMap;
import java.util.Map;
import org.json.JSONObject;
public class Scratch {
public static void main(String[] args) throws Exception {
String json = "{ \"labels\": { \"kubernetes.io/hostname\": \"192.168.200.176\", \"node\": \"master\" } }";
Map<String, String> library = new HashMap<>();
// parse the input string
JSONObject labels = new JSONObject(json).getJSONObject("labels");
// iterate over keys and insert into Map
for (String key : labels.keySet()) {
library.put(key, labels.getString(key));
}
System.out.println(library);
// {kubernetes.io/hostname=192.168.200.176, node=master}
}
}
You can also do more-or-less the same thing with Gson, by simply wrapping the Map in a container class, and letting it do the actual deserialization:
import java.util.Map;
import com.google.gson.Gson;
public class Scratch {
public static void main(String[] args) throws Exception {
String json = "{ \"labels\": { \"kubernetes.io/hostname\": \"192.168.200.176\", \"node\": \"master\" } }";
Library library = new Gson().fromJson(json, Library.class);
System.out.println(library.labels);
// {kubernetes.io/hostname=192.168.200.176, node=master}
}
}
class Library {
Map<String, String> labels;
}
In both cases, notice that you have to get the data from inside the "labels" field of the JSON, not the top level.
Related
I have a simple class in java like below:
class Simple {
private String name;
private String email;
}
I want to have behaviour of java.util.List<Simple> and Simple both according to input data that my program receives.
i.e.
Case 1::
if my program receives below kind of json-array input
{"simple" : [ {"name":"a", "email" : "a#z.com"}, {"name":"b", "email" : "b#z.com"} ]}
I need to parse it using List<Simple>
Case 2::
if my program receives below kind of json-object input
{"simple" : {"name":"c", "email" : "c#z.com"} }
I need to parse it using Simple
Note: I have tried using JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, but the problem is it is basically converting single value also into json-array at the time of writing json.
I need to persist json as it is, is there any other way to achieve this?
To avoid any Jackson customisation I would create wrapper class with an Object simple property. We can add two extra checking methods and two extra casting methods. It will allow Jackson to do it's logic and in runtime we can check what actually we have:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Collections;
import java.util.List;
public class DateApp {
private final static JsonMapper JSON_MAPPER = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
public static void main(String[] args) throws Exception {
Simple object = new Simple("John", "john#doe.com");
SimpleWrapper wrapper = new SimpleWrapper();
wrapper.setSimple(object);
serializeAndDeserialize(wrapper);
wrapper.setSimple(Collections.singletonList(object));
serializeAndDeserialize(wrapper);
}
private static void serializeAndDeserialize(SimpleWrapper wrapper) throws JsonProcessingException {
String json = JSON_MAPPER.writeValueAsString(wrapper);
System.out.println("JSON:");
System.out.println(json);
wrapper = JSON_MAPPER.readValue(json, SimpleWrapper.class);
System.out.println("Wrapper:");
System.out.println(wrapper);
}
}
#Data
class SimpleWrapper {
private Object simple;
#JsonIgnore
public boolean isSimpleObject() {
return simple instanceof Simple;
}
#JsonIgnore
public boolean isSimpleList() {
return simple instanceof List;
}
#JsonIgnore
public Simple getSimpleAsObject() {
return (Simple) simple;
}
#JsonIgnore
public List<Simple> getSimpleAsList() {
return (List<Simple>) simple;
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
class Simple {
private String name;
private String email;
}
Above code prints:
JSON:
{
"simple" : {
"name" : "John",
"email" : "john#doe.com"
}
}
Wrapper:
SimpleWrapper(simple={name=John, email=john#doe.com})
JSON:
{
"simple" : [ {
"name" : "John",
"email" : "john#doe.com"
} ]
}
Wrapper:
SimpleWrapper(simple=[{name=John, email=john#doe.com}])
You can use JsonNode.isArray() (or JsonNode.isObject()) to perform this check.
Then you can parse the node into a list with ObjectReader.readValue() or into a POJO using ObjectMapper.treeToValue().
String myJson = """
{"simple" : {"name":"c", "email" : "c#z.com"} }
""";
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(myJson);
if (node.isArray()) {
ObjectReader reader = mapper.readerFor(new TypeReference<List<Simple>>() {});
List<Simple> list = reader.readValue(node);
// do something with a list
} else {
Simple pojo = mapper.treeToValue(node, Simple.class);
// do something else with a single object
}
Jackson is able to parse any json into a map where value is any object. you can then inquire on the type of the map value
Map<String, Object> map = new ObjectMapper().readValue(jsonInput, Map.class);
Object value = map.get("simple");
if (value instanceof Collection) { // will return false for null
Collection<Simple> simples = (Collection<Simple>)value;
}
else if (value instanceof Simple) {
Simple simple = (Simple)value;
}
else {
System.err.println("unrecognized");
}
You only need to read the first node, simple and check if it is an array - using isArray() method.
public class Main{
public static void main(String args[]) {
//String inputString = [your input];
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(inputString);
JsonNode simpleNode = root.findPath("simple");
if(simpleNode.isArray()) {
//you have an array
} else {
// you have an element
}
}
}
Given the following JSON source
{
"database": "Accounts",
"groups": {
"databaseDictionary": {
"folder": "C:\\Development\\Work\\Ac\\Data\\DataDictionary"
},
"sqlCreateDatabase": {
"name": "Sleaford",
"user": "JBarrow",
"useWindowsAuthentication": "true"
},
"defaultData": {
"folder": "C:\\Development\\Work\\Ac\\Data\\Configuration"
}
}
}
I was hoping that the following class structure would successfully populate the object.
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
public class JsonNestedHashMapConcise {
private Configuration data;
private class Configuration {
private String database;
private HashMap<String, Group> groups;
private class Group {
private HashMap<String, String> settings;
}
}
public JsonNestedHashMapConcise (String fn) {
try (Reader jsonFile = new BufferedReader(new FileReader(new File(fn)))) {
Gson jsonParser = new Gson();
this.data = jsonParser.fromJson(jsonFile, Configuration.class);
}
catch (JsonSyntaxException | IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
JsonNestedHashMapConcise myConfiguration;
myConfiguration = new JsonNestedHashMapConcise("C:\\StackOverflow\\Concise.json");
System.out.println("Completed reading database configuration for " + myConfiguration);
}
}
However, while the Configuration.groups HashMap is populated, the nested groups.settings HashMap is null.
I have searched around and found possible solutions around techniques such as JsonDeserializer, registerTypeAdapter & TypeToken but I can’t comprehend how they fit into solving my problem.
As a bit of background, I have a starting point (source and sample JSON listed below) that provides a workaround but requires a more verbose JSON syntax. It was while writing the supporting methods I spotted that if I make the Configuration.groups field a HashMap, it would lead to more efficient queries. I have also read that inner classes were supported in Gson and this starting point appears to support that statement.
JSON source for verbose structure
{
"database": "Accounts",
"groups": [
{
"name": "databaseDictionary",
"settings": {
"folder": "C:\\Development\\Work\\Ac\\Data\\DataDictionary"
}
},{
"name": "sqlCreateDatabase",
"settings": {
"name": "Sleaford",
"user": "JBarrow",
"useWindowsAuthentication": "true"
}
},{
"name": "defaultData",
"settings": {
"folder": "C:\\Development\\Work\\Ac\\Data\\Configuration"
}
}
]
}
works with the following class structure
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
public class JsonNestedHashMapVerbose {
private Configuration data;
private class Configuration {
private String database;
private ArrayList<Group> groups;
private class Group {
private String name;
private HashMap<String, String> settings;
}
}
public JsonNestedHashMapVerbose (String fn) {
try (Reader jsonFile = new BufferedReader(new FileReader(new File(fn)))) {
Gson jsonParser = new Gson();
this.data = jsonParser.fromJson(jsonFile, Configuration.class);
}
catch (JsonSyntaxException | IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
JsonNestedHashMapVerbose myConfiguration;
myConfiguration = new JsonNestedHashMapVerbose("C:\\StackOverflow\\Concise.json");
System.out.println("Completed reading database configuration for " + myConfiguration);
}
}
The only difference I can spot between the two designs is that the starting structure has an explicit ‘settings’ element defined within the JSON which may be needed for the parser to be able to deserialize that part of the structure.
Am I pushing the Gson deserializer too far or is there some additional code I can add to help Gson complete the task?
Feedback on suggested duplicate question
The solution detailed in the suggested related question, using a parser provided by creates a very verbose class structure which I have detailed below (edited for clarity).
This automated parser method doesn't recognise that these related elements (databaseDictionary, sqlCreateDatabase & defaultData) can all be stored in a HashMap structure, and I would have been very impressed if the parser could have detected that. Instead, we end up with a class for each and every element.
public class DatabaseDictionary {
public String folder;
}
public class DefaultData {
public String folder;
}
public class Example {
public String database;
public Groups groups;
}
public class Groups {
public DatabaseDictionary databaseDictionary;
public SqlCreateDatabase sqlCreateDatabase;
public DefaultData defaultData;
}
public class SqlCreateDatabase {
public String name;
public String user;
public String useWindowsAuthentication;
}
The JSON source detailed at the start of this question is a subset of the number of elements existing within the groups element and these will be expanded (and later searched on).
Using the class structure detailed using would require that I create a new class to support each and every element (and I expect there to be 10s or 100s of these elements within the source). Using this structure would have also meant that it was very hard to locate a specified group element within the groups element to extract its attributes.
I can parse the JSON source using the primitive methodology of JsonToken to populate the java class
private class Configuration {
private String database;
private HashMap<String, Group> groups;
private class Group {
private HashMap<String, String> settings;
}
}
but, given that the GSON parser clearly can detect and populate HashMap elements as illustrated in the second example, I had hoped that a two tier HashMap would be detected in the same manner to avoid the repeated use of JsonToken as I have other similar JSON structures that would benefit from the use of HashMap.
I don't know whether the GSON parser detected the class type in the supplied java class or it detected that there were multiple element names at the same level in order to populate a single HashMap as I don't have a knowledge of the internal workings of GSON.
I was hoping that there was a supporting GSON abstract method that I could use to educate the parser into populating the hierarchical HashMap class.
In case anyone is interested
If anyone else wants to model a similar structure, I found that using the JsonReader class led to quite a readable solution even though it would be nice if the toJson method would have just worked as minimalist code to write. I have provided my source below, although as I am still a bit of a novice, I am sure that there may be more elegant ways to write some of the methods.
import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.JsonReader;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
public class DatabaseConfiguration {
private String database;
private HashMap<String, Group> groups;
private class Group {
private HashMap<String, String> settings;
}
private Group ReadGroup(JsonReader jsonReader) throws IOException {
Group result = null;
// Iterate over the the list of group attributes
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (result == null) {
result = new Group();
result.settings = new HashMap<>();
}
result.settings.put(name, jsonReader.nextString());
}
jsonReader.endObject();
return result;
}
private HashMap<String, Group> ReadGroups (JsonReader jsonReader) throws IOException {
HashMap<String, Group> result = null;
// Iterate over the the list of groups
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
Group myGroup;
myGroup = ReadGroup(jsonReader);
if (result == null) {
result = new HashMap<>();
}
result.put(name, myGroup);
}
jsonReader.endObject();
return result;
}
public DatabaseConfiguration (String fn) {
try (Reader jsonFile = new BufferedReader(new FileReader(new File(fn)));
JsonReader jsonReader = new JsonReader(jsonFile)) {
// Root node (database + groups)
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
switch (name) {
case "database":
this.database = jsonReader.nextString();
break;
case "groups":
this.groups = ReadGroups(jsonReader);
break;
default:
throw new JsonSyntaxException("Unexpected name " + name + "in outer element of " + fn);
}
}
jsonReader.endObject();
}
catch (JsonSyntaxException | IOException e) {
System.out.println(e);
}
}
public static void main(String[] args) {
DatabaseConfiguration myConfiguration;
myConfiguration = new DatabaseConfiguration("C:\\StackOverflow\\Concise.json");
System.out.println("Completed reading database configuration for " + myConfiguration);
}
}
An aside as my first time posting here
My original question was originally marked as a duplicate and so was automatically closed. I then, as advised, edited my question to detail why the suggested solution in the duplicate question was not related to my question. I then expected the question to be re-opened (as it had been edited) but this never happened. I didn't want to cut-and-paste my edited question to re-issue it as I felt that would be abusing the principles behind Stack Overflow and so I leave confused as to what the correct methodology is to successfully retract a question incorrectly marked as a duplicate.
Since I commented above, I have now discovered (by accident) that I can ask the originator of the 'duplicate' marker to review their decision by 'pinging' them in a comment so I will now try that!
It would be nice if there was an obvious option after narrating why the original question isn't a duplicate on the form but as I am a new user, I can't post any feedback in the general forum until I have 5 credits!
I use Jackson to serialize/deserialize JSON.
I have a List<String> in which all elements inside are already serialized in JSON format. I would like to generate a big JSON from that List.
In other word, I have:
List<String> a = new ArrayList<>();
a[0] = JSON_0
a[1] = JSON_1
...
a[N] = JSON_N
And I would like to render:
[
{JSON_0},
{JSON_1},
...
{JSON_N}
]
What is the best way to do so using Jackson?
Probably the simpler solution would be to create ArrayNode and use addRawValue method:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.util.RawValue;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
ArrayNode nodes = mapper.getNodeFactory().arrayNode();
nodes.addRawValue(new RawValue("{}"));
nodes.addRawValue(new RawValue("true"));
nodes.addRawValue(new RawValue("{\"id\":1}"));
System.out.println(mapper.writeValueAsString(nodes));
}
}
Above code prints:
[{},true,{"id":1}]
You can also, create a POJO with list and use #JsonRawValue annotation. But if you can not have extra root object you need to implement custom serialiser for it. Example with POJO and custom serialiser:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
List<String> jsons = new ArrayList<>();
jsons.add("{}");
jsons.add("true");
jsons.add("{\"id\":1}");
RawJsons root = new RawJsons();
root.setJsons(jsons);
System.out.println(mapper.writeValueAsString(root));
}
}
#JsonSerialize(using = RawJsonSerializer.class)
class RawJsons {
private List<String> jsons;
public List<String> getJsons() {
return jsons;
}
public void setJsons(List<String> jsons) {
this.jsons = jsons;
}
}
class RawJsonSerializer extends JsonSerializer<RawJsons> {
#Override
public void serialize(RawJsons value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartArray();
if (value != null && value.getJsons() != null) {
for (String json : value.getJsons()) {
gen.writeRawValue(json);
}
}
gen.writeEndArray();
}
}
If you need to have SerializationFeature.INDENT_OUTPUT feature enabled for all items in array, you need to deserialise all inner objects and serialise them again.
See also:
How can I include raw JSON in an object using Jackson?
Handling raw JSON values using Jackson
the simple fact of having the character '[' we are marking that it is an array so what I recommend to put the list into a JSON array.
I would need a little more information to help you, since it doesn't make much sense to use a JSON String, since a JSON is composed of Key / Value, it is best to make a bean / object with the attribute.
Example:
class Object {
private String attribute = value;
}
{attribute: value}
I'm using Jackson to serialize and deserialize JSON objects to and from POJO-s.
One of the elements I'm trying to create is a list of arrays (I think that's what this would be called).
"Images": [
{}
],
I've tried:
public ArrayList<String> Images = new ArrayList<String>();
and then just didn't add anything to it then called the object mapper.
That unfortunately gave me just a list:
"Images":[
]
I then tried to make it an list of string arrays:
public ArrayList<String[]> Images = new ArrayList<String[]>();
I added an empty array to my ArrayList:
String[] tempArray = {};
Images.add(tempArray);
But that gave me:
"Images":[
[]
],
How can I get the needed format?
What you describing is a list of objects, which would be:
public ArrayList<yourObject> Images = new ArrayList<yourObject>();
This would give you the:
"Images": [
{}
]
If you want a list of arrays, you would do:
public ArrayList<ArrayList<yourObject> Images = new ArrayList<ArrayList<yourObject>();
this would give you:
"Images":[
[]
]
JSON not-primitives in Java can be represented by:
JSON Array - [] => java.util.List or array.
JSON Object - {} => java.util.Map or POJO class.
If you need empty JSON Object you can use empty map or empty POJO - POJO without properties or with all properties set to null and with ObjectMapper set to ignore null-s.
Simple example which generates desired output could look like below:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(new Root());
System.out.println(json);
}
}
class Root {
private static final List<Map<String, Object>> EMPTY_MAP_IN_ARRAY = Collections.singletonList(Collections.emptyMap());
#JsonProperty("Images")
private List<Map<String, Object>> images = EMPTY_MAP_IN_ARRAY;
public List<Map<String, Object>> getImages() {
return images;
}
public void setImages(List<Map<String, Object>> images) {
this.images = images;
}
}
Above code prints:
{"Images":[{}]}
I have some json and it's fairly complex -- (a bit too complex and open-ended to model using something like gson), and I need to extract string values from certain nodes into a list of strings.
The following code works, but due to the way my json works -- it's grabbing lots of extra stuff that I don't want (note: I don't own the json schema)
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(json);
List<JsonNode> keys = node.findValues("key") ;
for(JsonNode key: keys){
System.out.println(key.toString());
}
The contents of Json is fairly complex (Jira filter export) which looks like this:
{
"issues": [
{
"key":"MIN-123",
...
"fields":{
"key":"A_Elric"
}
}
]
}
Assertions:
I always want to extract issues[x].key and not any of the subkeys. I would prefer to extract this into a list, but any normal data structure is fine. I'm already using Jackson -- but gson is also an option if there's a sane way of doing so.
Thanks for the assist!
JsonPath is xpath for json, and it has a Java implementation.
Here is a working example to get issue keys without subkeys:
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
public class JsonPathTest {
public static String ROOT_ARRAY = "issues";
public static String KEY = "key";
// get all KEYs right under ROOT array
public static String jsonPath = String.format("$.%s[*].%s", ROOT_ARRAY, KEY);
public static void main(String[] args) {
try {
String jsonStr = new String(Files.readAllBytes(Paths.get("c:/temp/xx.json")));
Object jsonObj = Configuration.defaultConfiguration().jsonProvider().parse(jsonStr);
List<String> keys = JsonPath.parse(jsonObj).read(jsonPath);
System.out.println(keys);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ExportFilter{
private static final String KEY = "key";
private List<Map<String,Object>> issues = new ArrayList<>();
//getters and setters
#JsonIgnore
public List<String> getKeys(){
return issues.stream()
.map(issue-> issue.get(KEY))
.filter(Objects::nonNull)
.map(Objects::toString)
.collect(toList());
}
}
Example usage:
ObjectMapper objectMapper = new ObjectMapper();
List<String> keys = objectMapper.readValue( .., ExportFilter.class).getKeys();