How do I map to HashMap for array? - java

I have a JSON I want a to convert it to a HashMap. I have the following code -
ObjectMapper mapper = new ObjectMapper();
Map<String, String> jsonData = new HashMap<String, String>();
jsonData = mapper.readValue(userPropertyJson, new TypeReference<HashMap<String,String>>(){});
it is working fine if the input JSON is
{"user":1, "entity": "email"}
but fails when the JSON is as below -
{"user":1, "entity": ["email","fname","lname","phone"]}
How do I map to HashMap for array also?

Declare a generic HashMap with String as a key and Object as a value , since you don't know the type of value exactly.
Map<String, Object>
And beware of assigning wrong types while retrieving data

Use Map<String, Object>. Example
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonParser {
public static void main(String[] args) {
String userPropertyJson = "{\"user\":1, \"entity\": [\"email\",\"fname\",\"lname\",\"phone\"]}";
ObjectMapper mapper = new ObjectMapper();
try {
Map<String, Object> jsonData = new HashMap<String, Object>();
jsonData = mapper.readValue(userPropertyJson, new TypeReference<HashMap<String,Object>>(){});
System.out.println(jsonData);
} catch (JsonParseException e) {
System.out.println(e.getMessage());
} catch (JsonMappingException e) {
System.out.println(e.getMessage());
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}

If you know in advance that your json will always have the same format (a String key mapped to a List<String>, either with a single element or with many elements), then you could use the ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature:
ObjectMapper mapper = new ObjectMapper()
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
String jsonWithArray =
"{\"user\": 1, \"entity\": [\"email\", \"fname\", \"lname\", \"phone\"]}";
Map<String, List<String>> map1 =
mapper.readValue(
jsonWithArray,
new TypeReference<HashMap<String, List<String>>>() {});
System.out.println(map1); // {user=[1], entity=[email, fname, lname, phone]}
String jsonWithoutArray = "{\"user\": 1, \"entity\": \"email\"}";
Map<String, List<String>> map2 =
mapper.readValue(
jsonWithoutArray,
new TypeReference<HashMap<String, List<String>>>() {});
System.out.println(map2); // {user=[1], entity=[email]}
This enables you to either have an array for the values in your json, or a single element.

Check out http://www.jsonschema2pojo.org/
It allows you to convert json to java object automatically. I use it when I need to create DTOs from a web service for which I don't have java mapping or SDK.

Related

Convert JSON record to LinkedHashMap<String,String> using Jackson API

I have a JSON file(it contains an array of JSON objects.)
I am trying to read it object by object.
Each object I need to convert it to a LinkedHashMap<String,String> where both the key and value are strings. Note that even if the JSON objects contain a non-string value(Integer/Boolean), I want my LinkedHashMap to contain a string.
This is my JSON file (films.json):
[
{
"name": "Fight Club",
"year": 1999,
}
]
Now, this has 1 object. I want to convert it to a LinkedHashMap<String,String>.
So for the above example, my LinkedHashMap should contain(for the 1st JSON object) :
"name" : "Fight CLub"
"year" : "1999"
Notice how the year is String in the LinkedHashMap and not Integer.
This is what I tried.
Map<String, Object> myLinkedHashMap;
JsonParser jsonParser = new JsonFactory().createParser(new File("films.json"));
jsonParser = new JsonFactory().createParser(new File(filePath));
jsonParser.nextToken();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
while(jsonParser.nextToken() != JsonToken.END_ARRAY){
myLinkedHashMap = mapper.readValue(jsonParser, LinkedHashMap.class);
}
The variable myLinkedHashMap will contain a key/value pair for an object in my JSON file.
But the problem is that for 'year' of the JSON file, I am getting Integer in the LinkedHashMap as the JSON file also contains Integer.
Instead, I want the Integer as String in the LinkedHashMap.
Please help me get String in the LinkedHashMap instead of Integer.
Note: The solution should be generic to other data types also.
So if the JSON object contains boolean true, then my LinkedHashMap should contain "true".
You can construct map type using TypeFactory and constructMapType method to tell exactly what do you need from readValue method. See below example:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.util.Assert;
import java.io.File;
import java.util.LinkedHashMap;
public class JsonMapApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
JsonParser jsonParser = mapper.getFactory().createParser(jsonFile);
jsonParser.nextToken();
MapType mapType = mapper.getTypeFactory().constructMapType(LinkedHashMap.class, String.class, String.class);
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
LinkedHashMap<String, String> map = mapper.readValue(jsonParser, mapType);
map.forEach((k, v) -> {
Assert.isInstanceOf(String.class, v);
System.out.println(k + " -> " + v + " (" + v.getClass().getName() + ")");
});
}
}
}
Above code prints:
name -> Fight Club (java.lang.String)
year -> 1999 (java.lang.String)
Change
Map<String, Object> myLinkedHashMap;
to
Map<String, String> myLinkedHashMap;

Escape String backslash using GSON

I am trying to convert my hashmap to a JSON pretty print output.
I tried GSON and believe there is some issue with it handling string as inputs inside a map, is there any other way to do this?
Map<String, String> map = new LinkedHashMap();
Gson gson = new GsonBuilder().enableComplexMapKeySerialization().setPrettyPrinting().create();
map.put("Intro", Map_to_String);
map.put("Output", String_Val);
System.out.println(gson.toJson(map));
Output:
{
"Intro": {\"No\":0,\"Cast\":2},
"Output": "123"
}
Required Output:
{
"Intro": {"No":0,"Cast":2},
"Output": "123"
}
You need to deserialise Map_to_String back to object - Map in this case and after that serialise again.
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
public class GsonApp {
public static void main(String[] args) {
Map<String, Object> map = new LinkedHashMap<>();
Gson gson = new GsonBuilder()
.enableComplexMapKeySerialization()
.setPrettyPrinting()
.create();
String jsonString = "{\"No\":0,\"Cast\":2}";
Type mapType = new TypeToken<Map<String, String>>() {}.getType();
map.put("Intro", gson.fromJson(jsonString, mapType));
map.put("Output", "123");
System.out.println(gson.toJson(map));
}
}
Prints:
{
"Intro": {
"No": "0",
"Cast": "2"
},
"Output": "123"
}

jackson-dataformat-csv: Mapping number value without POJO

I'm trying to parse a CSV file using jackson-dataformat-csv and I want to map the numeric column to the Number java type.
CsvSchema schema = CsvSchema.builder().setUseHeader(true)
.addColumn("firstName", CsvSchema.ColumnType.STRING)
.addColumn("lastName", CsvSchema.ColumnType.STRING)
.addColumn("age", CsvSchema.ColumnType.NUMBER)
.build();
CsvMapper csvMapper = new CsvMapper();
MappingIterator<Map<String, Object>> mappingIterator = csvMapper
.readerFor(Map.class)
.with(schema)
.readValues(is);
while (mappingIterator.hasNext()) {
Map<String, Object> entryMap = mappingIterator.next();
Number age = (Number) entryMap.get("age");
}
I'm expecting entryMap.get("age") should be a Number, but I get String instead.
My CSV file:
firstName,lastName,age
John,Doe,21
Error,Name,-10
I know that CsvSchema works fine with POJOs, but I need to process arbitrary CSV schemas, so I can't create a new java class for every case.
Any way to parse CSV into a typed Map or Array?
Right now it is not possible to configure Map deserialisation using CsvSchema. Process uses com.fasterxml.jackson.databind.deser.std.MapDeserializer which right now does not check schema. We could write custom Map deserialiser. There is a question on GitHub: CsvMapper does not respect CsvSchema.ColumnType when using #JsonAnySetter where cowtowncoder answered:
At this point schema type is not used much for anything, but I agree
it should.
EDIT
I decided to take a look closer what we can do with that fact that com.fasterxml.jackson.databind.deser.std.MapDeserializer is used behind the scene. Implementing custom Map deserialiser which will take care about types would be tricky to implement and register but we can use knowledge about ValueInstantiator. Let's define new Map type which knows what to do with ColumnType info:
class CsvMap extends HashMap<String, Object> {
private final CsvSchema schema;
private final NumberFormat numberFormat = NumberFormat.getInstance();
public CsvMap(CsvSchema schema) {
this.schema = schema;
}
#Override
public Object put(String key, Object value) {
value = convertIfNeeded(key, value);
return super.put(key, value);
}
private Object convertIfNeeded(String key, Object value) {
CsvSchema.Column column = schema.column(key);
if (column.getType() == CsvSchema.ColumnType.NUMBER) {
try {
return numberFormat.parse(value.toString());
} catch (ParseException e) {
// leave it as it is
}
}
return value;
}
}
For new type without no-arg constructor we should create new ValueInstantiator:
class CsvMapInstantiator extends ValueInstantiator.Base {
private final CsvSchema schema;
public CsvMapInstantiator(CsvSchema schema) {
super(CsvMap.class);
this.schema = schema;
}
#Override
public Object createUsingDefault(DeserializationContext ctxt) {
return new CsvMap(schema);
}
#Override
public boolean canCreateUsingDefault() {
return true;
}
}
Example usage:
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.HashMap;
public class CsvApp {
public static void main(String[] args) throws IOException {
File csvFile = new File("./resource/test.csv").getAbsoluteFile();
CsvSchema schema = CsvSchema.builder()
.addColumn("firstName", CsvSchema.ColumnType.STRING)
.addColumn("lastName", CsvSchema.ColumnType.STRING)
.addColumn("age", CsvSchema.ColumnType.NUMBER)
.build().withHeader();
// Create schema aware map module
SimpleModule csvMapModule = new SimpleModule();
csvMapModule.addValueInstantiator(CsvMap.class, new CsvMapInstantiator(schema));
// register map
CsvMapper csvMapper = new CsvMapper();
csvMapper.registerModule(csvMapModule);
// get reader for CsvMap + schema
ObjectReader objectReaderWithSchema = csvMapper
.readerWithSchemaFor(CsvMap.class)
.with(schema);
MappingIterator<CsvMap> mappingIterator = objectReaderWithSchema.readValues(csvFile);
while (mappingIterator.hasNext()) {
CsvMap entryMap = mappingIterator.next();
Number age = (Number) entryMap.get("age");
System.out.println(age + " (" + age.getClass() + ")");
}
}
}
Above code for below CSV payload:
firstName,lastName,age
John,Doe,21
Error,Name,-10.1
prints:
21 (class java.lang.Long)
-10.1 (class java.lang.Double)
It looks like a hack but I wanted to show this possibility.
You can use univocity-parsers for this sort of thing. It's faster and way more flexible:
CsvParserSettingssettings = new CsvParserSettings(); //configure the parser if needed
CsvParser parser = new CsvParser(settings);
for (Record record : parser.iterateRecords(is)) {
Short age = record.getShort("age");
}
To get a typed map, tell the parser what is the type of the columns you are working with:
parser.getRecordMetadata().setTypeOfColumns(Short.class, "age" /*, and other column names*/);
//to get 0 instead of nulls when the field is empty in the file:
parser.getRecordMetadata().setDefaultValueOfColumns("0", "age", /*, and other column names*/);
// then parse
for (Record record : parser.iterateRecords(is)) {
Map<String,Object> map = record.toFieldMap();
}
Hope this helps
Disclaimer: I'm the author of this library. It's open source and free (Apache 2.0 license)

Expected BEGIN_OBJECT but was STRING in Gson

Hi I am try to parse some JSON by GSON which used number as the key.
I reference the post but it give some error and I don't know why.
How to convert json objects with number as field key in Java?
I also see the post but still cannot solve my problem.
"Expected BEGIN_OBJECT but was STRING at line 1 column 1"
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Gson gson = new Gson();
Type type = new TypeToken<HashMap<String, HashMap<String, String>>>() {}.getType();
Map<String, Map<String, String>> map = gson.fromJson("./src/main/resources/input.json", type);
}
}
The json file is
{
"1":{"id":"1"}
}
The fromJson method doesn't receive a filename, it receives an actual JSON: look at the docs here
But there is an overload that receives a Reader instead:
try (FileReader reader = new FileReader("./src/main/resources/input.json"))
{
map = gson.fromJson(reader, type)
}
catch (...) { ... }

Create hashmap with arraylist of hashmap

I have the following structure
{
"abc":[
{
"bc":"52",
"gd":"jjksa"
}
]
}
Now I need to create the same structure from the client side and send the data similar like the above format. I am assuming the structure to be something like this.
HashMap<String, ArrayList<HashMap<String, String>>> hashmap = new HashMap<String, ArrayList<new HashMap<String, String>()>>();
HashMap<String, String> obj1 = new HashMap<String, String>();
ArrayList<String> arraylist = new ArrayList<String>();
obj1.put("bc", "52");
obj1.put("gd", "jjksa");
arraylist.add(obj1);
hashmap.put("metrics", arraylist)
Anyone who can help me create the proper map as shown in the above example.
Use a JSON processor such as Jackson:
import org.codehaus.jackson.map.ObjectMapper;
// ...
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(hashmap);
The below code worked for me. However the suggestion that you guys have put forward is surely valuable. I will make the changes accordingly to make the structure better.
HashMap<String, Object> hashmap = new HashMap<String, Object>();
HashMap<String, String> obj1 = new HashMap<String, String>();
ArrayList arraylist = new ArrayList();
obj1.put("bc", "52");
obj1.put("gd", "jjksa");
arraylist.add(obj1);
hashmap.put("metrics", arraylist);
Gson json = new Gson();
System.out.print(json.toJson(hashmap));
Simple the HashMap's toString method prints the value excatly like your json but without the double-quotes("). :)
sp00m's answer works perfectly. In addition to that you could also use google's Gson.
import java.util.ArrayList;
import java.util.HashMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
public class GsonParser {
public static void main(String[] args) throws Exception {
HashMap<String, ArrayList<HashMap<String, String>>> hashmap = new HashMap<String, ArrayList<HashMap<String, String>>>();
HashMap<String, String> obj1 = new HashMap<String, String>();
ArrayList<HashMap<String, String>> arraylist = new ArrayList<HashMap<String, String>>();
obj1.put("bc", "52");
obj1.put("gd", "jjksa");
arraylist.add(obj1);
//Hashmap toString
hashmap.put("metrics", arraylist);
System.out.println(hashmap); //Prints : {metrics=[{bc=52, gd=jjksa}]}
//Jackson Example
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(hashmap);
System.out.println(json); //Prints : {"metrics":[{"bc":"52","gd":"jjksa"}]}
//Gson Example
Gson gson = new Gson();
String json2 = gson.toJson(hashmap);
System.out.println(json2); //Prints : {"metrics":[{"bc":"52","gd":"jjksa"}]}
}
}

Categories