I'm trying to dynamically parse some JSON to a Map. The following works well with simple JSON
String easyString = "{\"name\":\"mkyong\", \"age\":\"29\"}";
Map<String,String> map = new HashMap<String,String>();
ObjectMapper mapper = new ObjectMapper();
map = mapper.readValue(easyString,
new TypeReference<HashMap<String,String>>(){});
System.out.println(map);
But fails when I try to use some more complex JSON with nested information. I'm trying to parse the sample data from json.org
{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": [
"GML",
"XML"
]
},
"GlossSee": "markup"
}
}
}
}
}
I get the following error
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
Is there a way to parse complex JSON data into a map?
I think the error occurs because the minute Jackson encounters the { character, it treats the remaining content as a new object, not a string. Try Object as map value instead of String.
public static void main(String[] args) throws IOException {
Map<String,String> map = new HashMap<String,String>();
ObjectMapper mapper = new ObjectMapper();
map = mapper.readValue(x, new TypeReference<HashMap>(){});
System.out.println(map);
}
output
{glossary={title=example glossary, GlossDiv={title=S, GlossList={GlossEntry={ID=SGML, SortAs=SGML, GlossTerm=Standard Generalized Markup Language, Acronym=SGML, Abbrev=ISO 8879:1986, GlossDef={para=A meta-markup language, used to create markup languages such as DocBook., GlossSeeAlso=[GML, XML]}, GlossSee=markup}}}}}
Wrap your Map into a dumb object as container, like this:
public class Country {
private final Map<String,Map<String,Set<String>>> citiesAndCounties=new HashMap<>;
// Generate getters and setters and see the magic happen.
}
The rest is just working with your Object mapper, example Object mapper using Joda module:
public static final ObjectMapper JSON_MAPPER=new ObjectMapper().
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).
setSerializationInclusion(JsonInclude.Include.NON_NULL).
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).
registerModule(new JodaModule());
// Calling your Object mapper
JSON_MAPPER.writeValueAsString(new Country());
Hope that helps ;-)
Related
I have two JSON files, called "pickevent1" and "pickevent2". I have to compare if both files are matching; if they don't match, I need to know where they don't match.
pickevent1
{
"pickEventActivities": [{
"orderId": "215",
"lineNbr": 0,
"pick": "EACH",
"activations": [{
"activationType": "Si",
"activationValue": "31"
}]
}]
}
pickevent2
{
"pickEventActivities": [{
"orderId": "115",
"lineNbr": 1,
"pick": "Hello",
"activations": [{
"activationType": "Bi",
"activationValue": "3"
}]
}]
}
I created a pick event POJO class:
#JsonRootName(value = "pickEventActivities")
#Data
#JsonPropertyOrder({ "orderId", "lineNbr", "pick"})
class PickEvent {
String orderId;
String lineNbr;
String pick;
List<Activation> activations;
}
and a Activation POJO class:
#Data
#JsonPropertyOrder({ "activationType", "activationValue"})
public class Activation {
String activationType;
String activationValue;
}
To make sure it works, I created a test class:
public void compareJson() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
PickEvent result1 = objectMapper.readValue(new File("src/../pickevent1.json"), PickEvent.class);
PickEvent result2 = objectMapper.readValue(new File("src/../pickevent2.json"), PickEvent.class);
assertEquals(result1, result2);
}
But when I am doing assertSame(result1,result2) its giving me null for json values:
Exception in thread "main" java.lang.AssertionError: expected same:<PickEvent(orderId=null, lineNbr=null, pick=null, activations=null)> was not:<PickEvent(orderId=null, lineNbr=null, pick=null, activations=null)>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotSame(Assert.java:828)
at org.junit.Assert.assertSame(Assert.java:771)
at org.junit.Assert.assertSame(Assert.java:782)
at JsonDiff.PickEventDiff.comparejson(PickEventDiff.java:26)
at JsonDiff.PickEventDiff.main(PickEventDiff.java:32)
It should give me an assertion error, but the test succeeds.
It should give me an assertion error, but the test succeeds.
Because you use objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);. In fact, an exception occurred during the parsing process.
Try:
public void compareJson() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
Wrapper wrapper = objectMapper.readValue(new File(""), Wrapper.class);
Wrapper wrapper2 = objectMapper.readValue(new File(""), Wrapper.class);
System.out.println(wrapper.equals(wrapper2));
}
#Data
static class Wrapper {
List<PickEvent> pickEventActivities;
}
You are trying to read a PickEvent Object but you're actually sending a list there.
Please change your json to
{
"pickEventActivities": {
"orderId": "115",
"lineNbr": 1,
"pick": "Hello",
"activations": [{
"activationType": "Bi",
"activationValue": "3"
}]
}
}
or try changing your code to
List<PickEvent> list1 = objectMapper.readValue(new File("src/../pickevent1.json"), new ParameterizedTypeReference<PickEvent>(){});
Here is my github repo for Intellij Idea plugin.
Basically JSON comparator implemented in java. It compares JSON by fields, values and objects in array
I'm trying to parse a json file that looks like this
{
"foo": "v2",
"bar": [
"abc/bcf<object#twenty>.xyz",
"abc/fgh<object#thirtu>.xyz"
]
}
The code I have is currently this:
Config.java
private static final ObjectMapper OBJECT_MAPPER;
static {
OBJECT_MAPPER = new ObjectMapper();
OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
OBJECT_MAPPER.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
OBJECT_MAPPER.enableDefaultTyping();
}
#JsonCreator
public Config(
#JsonProperty(value = "foo", required = true) final String version,
#JsonProperty(value = "bar") final List<String> barTypes) {
// rest of constructor
}
public static Config fromJson(final Reader reader)
throws IOException {
return OBJECT_MAPPER.readValue(reader, Config.class);
}
I am getting an error:
Failed to parse type 'abc/bcf<object#twenty>.xyz' (remaining: '<object#twenty>.xyz'): Cannot locate class 'abc/bcf', problem: abc/bcf"
Is there something special I need to do in order to read "<" as String?
I'm reading the file into a BufferReader with StandardCharsets.UTF_8 like this:
try (BufferedReader reader = Files.newBufferedReader(configFile.toPath(), StandardCharsets.UTF_8)) {
config = Config.fromJson(reader);
}
Edit: I actually do need defaultTyping for an ArrayList that has polymorphic types:
"Vehicles": [
"java.util.ArrayList",
[
{
"name": "Car"
},
{
"name": "Train"
}
]
I currently use a MixIn for declaring the subtypes. However, this stops working if I remove DefaultTyping.
Remove OBJECT_MAPPER.enableDefaultTyping(); and it will work fine. This method is anyway deprecated.
In case you want to read automatic polymorphic types, use activateDefaultTyping(PolymorphicTypeValidator ptv).
I have these messages arriving from SQS:
{
"eventID": "zzz",
"eventName": "MODIFY",
"eventVersion": "1.1",
"eventSource": "aws:dynamodb",
"awsRegion": "us-east-1",
"dynamodb": {
"ApproximateCreationDateTime": 1521976320,
"Keys": {
"key_1": {
"S": "yyy"
},
"key_2": {
"S": "xxx"
}
},
"SequenceNumber": "123",
"SizeBytes": 321,
"StreamViewType": "KEYS_ONLY"
},
"eventSourceARN": "arn:aws:dynamodb:us-east-1:eventSourceARN",
"itemType": "myItem"
}
I want to use gson library to convert this json string into a Record object (com.amazonaws.services.dynamodbv2.model.Record) which contains a StreamRecord object (com.amazonaws.services.dynamodbv2.model.StreamRecord) that represents the dynamodb sub json.
problem is that the inner fields of the dynamodb object are PascalCase while the other fields are normal camelCase.
This code:
Gson gson = new GsonBuilder()
//.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.create();
String json = <the json from the example above>
Record record = gson.fromJson(json, Record.class);
log.info("record="+record.toString());
StreamRecord dynamodb = record.getDynamodb();
log.info("dynamodb="+dynamodb.toString());
Map<String, AttributeValue> keys = dynamodb.getKeys();
log.info("keys="+keys.toString());
prints this log (UPPER_CAMEL_CASE commented out) :
record={EventID: zzz,EventName: MODIFY,EventVersion: 1.1,EventSource: aws:dynamodb,AwsRegion: us-east-1,Dynamodb: {},}
and then throws Null Pointer exception because the dynamoDb object is empty - because my json string is UPPER_CAMEL_CASE, while in the object its normal camelCase.
I want to apply FieldNamingPolicy.UPPER_CAMEL_CASE only for the dynamodb sub json.
perhaps somehow using FieldNamingStrategy ?
The json is given and I cannot change its schema.
I also can't change the fact that I get it as string.
see AWS API:
https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_streams_Record.html
https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_streams_StreamRecord.html
You seem to want the following naming strategy:
private static final Gson gson = new GsonBuilder()
.setFieldNamingStrategy(field -> {
if ( field.getDeclaringClass() == StreamRecord.class ) {
return FieldNamingPolicy.UPPER_CAMEL_CASE.translateName(field);
}
return FieldNamingPolicy.IDENTITY.translateName(field);
})
.create();
I usually never use naming strategies in favor of the #SerializedName annotation though, just to be more precise when declaring mappings.
I have a Json file :
[
{
"name":"Move",
"$$hashKey":"object:79",
"time":11.32818,
"endTime":18.615535
},
{
"name":"First Red Flash",
"$$hashKey":"object:77",
"time":15.749153
},
{
"name":"Pills",
"subEventTypes":[
"pull down bottle",
"unscrew lid",
"dump pills out",
"screw lid on",
"put bottle away"
],
"$$hashKey":"object:82",
"time":25.130175,
"subEventSplits":[
26.092057,
27.425881,
31.841594,
34.268093
],
"endTime":36.234827
}
]
I tried to parse this Json file using the Jackson.
I wrote the following code:
public class Holder{
public Holder(){};
//getter and setters
String name;
List<String> subEventTypes = new ArrayList<>();
Double time;
String $$hashKey;
Double endTime;
List<Double> subEventSplits = new ArrayList<>();
}
class MapperClass{
List<Holder> list = new ArrayList<>();
}
public static void main(String[] args) throws JsonProcessingException, IOException
{
ObjectMapper mapper = new ObjectMapper();
List<Holder> list = mapper.readValue(new File("data.json"), mapper.getTypeFactory().constructCollectionType(
List.class, Holder.class));
}
When I run the program, it showed this error : "
No suitable constructor found for type [simple type, class parseJason$Holder]: can not instantiate from JSON object (need to add/enable type information?)
".
Is there anything wrong with my code? or I have to use another way to parse my Json file.
try
list = mapper.readValue(
jsonString,
objectMapper.getTypeFactory().constructCollectionType(
List.class, Holder.class));
I want to deserialize a json like this
{
"0":{"name":"Alice"},
"1":{"name":"Bob"}
}
to a java collection( set or list, not map).
I want to change the default behavior of CollectionDeserializer to support this and config it as a global configuration. Any way to to this?
If you really have this structure (an object as a container and not an array, which could be handled much easier):
import com.fasterxml.jackson.databind.*;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
public class Main {
public static void main(String[] args) throws IOException {
String json = "{\"0\":{\"name\":\"Alice\"}, \"1\":{\"name\":\"Bob\"}}";
ObjectMapper mapper = new ObjectMapper();
JsonNode obj = mapper.readValue(json, JsonNode.class);
Iterator<Map.Entry<String, JsonNode>> userEntries = obj.fields();
while(userEntries.hasNext()){
Map.Entry<String, JsonNode> userEntry = userEntries.next();
System.out.println(userEntry.getKey() + " => " + userEntry.getValue());
}
}
}
You can achieve this task using gson api.
The code is as follows:
String yourJson = "{\"0\":{\"name\":\"Alice\"}, \"1\":{\"name\":\"Bob\"}}";
Gson gson = new Gson();
Type tarType = new TypeToken<Map<String,Map<String,String>>>(){
}.getType();
gson.fromJson(yourJson, tarType);
For this you need to add following:
com.google.gson.Gson
Why not to turn your JSON document into an array as:
{"persons":[{"name":"Alice"},{"name":"Bob"}]}
Then define a corresponding JSON schema (assuming PersonArray is the file name):
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Some description",
"type" : "object",
"properties" : {
"persons" : {
"type" : "array",
"items" : { "$ref": "#/definitions/person" }
}
},
"definitions": {
"person" : {
"type": "object",
"description": "A person",
"properties": {
"name": { "type": "string" }
}
}
}
}
and take advantage of Jackson Data Binding API by using jsonschema2pojo-maven-plugin Maven plugin to generate POJOs in Java (alternatively, you can manually implement POJOs).
Once POJOs are generated, you can use ObjectMapper to deserialise JSON document in the following way:
ObjectMapper mapper = new ObjectMapper();
PersonArray personArray = mapper.readValue(serialisedJsonDocument, PersonArray.class);
The elements of your JSON document will be stored inside PersonArray object as:
List<Person> persons = new ArrayList<Person>();
You could also add additional properties to the Person object if you needed.