I have a JSON:
{
"firstField": "Something One",
"secondField": "Something Two",
"thirdField": [
{
"thirdField_one": "Something Four",
"thirdField_two": "Something Five"
},
{
"thirdField_one": "Something Six",
"thirdField_two": "Something Seven"
}
],
"fifthField": [
{
"fifthField_one": "Something… ",
"fifthField_two": "Something...",
"fifthField_three": 12345
},
{
"fifthField_one": "Something",
"fifthField_two": "Something",
"fifthField_three": 12345
}
]
}
I have my classes:
public static class MyClass {
#JsonProperty
private String firstField, secondField;
#JsonProperty
private ThirdField thirdField;
#JsonProperty
private FifthField fifthField;
public static class ThirdField {
private List<ThirdFieldItem> thirdField;
}
public static class ThirdFieldItem {
private String thirdField_one, thirdField_two;
}
public static class FifthField {
private List<FifthFieldItem> fifthField;
}
public static class FifthFieldItem {
private String fifthField_one, fifthField_two;
private int fifthField_three;
}
}
I'm deserializing them with Jackson library:
public void testJackson() throws IOException {
JsonFactory factory = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(factory);
File from = new File("text.txt"); // JSON I mentioned above
mapper.readValue(from, MyClass.class);
}
but I'm getting the Exception:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize
instance of Main$MyClass$ThirdField out of START_ARRAY token
You defined your thirdField and fifthField properties as arrays in your JSON. They need to be arrays or collections on your Java bean as well:
public static class MyClass {
#JsonProperty
private String firstField, secondField;
#JsonProperty
private Collection<ThirdField> thirdField;
#JsonProperty
private Collection<FifthField> fifthField;
/// ...
}
As you are going through and converting an existing JSON object into beans, keep in mind that JSON data is very much like a map. If you envision how you would map the data from a map into your object it really helps. Your ThirdField and FifthField objects need to map the definitions in your JSON. This is what your JSON says a ThirdField is:
{
"thirdField_one": "Something Four",
"thirdField_two": "Something Five"
}
Literally converting that to a Java bean gives you:
public class ThirdField implements Serializable {
private String thirdField_one;
private String thirdField_two;
// ...
}
You can add in your annotations etc, etc to get a full fledged bean. Do the same thing for your FifthField object.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Since it is not always possible to change your domain model (as answered by #Perception), below is how you could map your original object model to the desired JSON using MOXy.
Java Model
In this use case you can leverage the #XmlPath(".") extension. This tells MOXy to bring the contents of the target object into the sources node.
#XmlAccessorType(XmlAccessType.FIELD)
public static class MyClass {
private String firstField, secondField;
#XmlPath(".")
private ThirdField thirdField;
#XmlPath(".")
private FifthField fifthField;
#XmlAccessorType(XmlAccessType.FIELD)
public static class ThirdField {
private List<ThirdFieldItem> thirdField;
}
#XmlAccessorType(XmlAccessType.FIELD)
public static class ThirdFieldItem {
private String thirdField_one, thirdField_two;
}
#XmlAccessorType(XmlAccessType.FIELD)
public static class FifthField {
private List<FifthFieldItem> fifthField;
}
#XmlAccessorType(XmlAccessType.FIELD)
public static class FifthFieldItem {
private String fifthField_one, fifthField_two;
private int fifthField_three;
}
}
Conversion Code
The demo code below shows how to enable MOXy's JSON binding.
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {MyClass.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource json = new StreamSource("src/forum13600952/input.json");
MyClass myClass = unmarshaller.unmarshal(json, MyClass.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(myClass, System.out);
}
input.json/Output
Belos is the input from your question slightly reformatted to match the output produced by MOXy.
{
"firstField" : "Something One",
"secondField" : "Something Two",
"thirdField" : [ {
"thirdField_one" : "Something Four",
"thirdField_two" : "Something Five"
}, {
"thirdField_one" : "Something Six",
"thirdField_two" : "Something Seven"
} ],
"fifthField" : [ {
"fifthField_one" : "Something...",
"fifthField_two" : "Something...",
"fifthField_three" : 12345
}, {
"fifthField_one" : "Something",
"fifthField_two" : "Something",
"fifthField_three" : 12345
} ]
}
For More Information
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
Related
Having the class:
public class MyClass {
private String id;
private Map<String, Object> properties;
...
}
and using Jackson, can I tell Jackson (via annotation) to serialize the key values in the properties map to top level key values in the MyClass serialization?
For example, if properties contains a couple of key-values: fruit - apple, color - red I would like to generate a JSON that looks like this:
{
"if": "...",
"fruit": "apple",
"color": "red"
}
instead of:
{
"if": "...",
"properties": {
"fruit": "apple",
"color": "red"
}
}
How can I serialize the key values pairs of a map as top level keys in
the serialised JSON?
A possible way to solve your issue is create a custom serializer for your MyClass class and annotate your class with the JsonSerialize annotation:
#JsonSerialize(using = MyClassSerializer.class)
public class MyClass {
private String id;
private Map<String, Object> properties;
}
In the custom serializer you can iterate over the properties map and build the representation of your object like below:
public class MyClassSerializer extends JsonSerializer<MyClass> {
#Override
public void serialize(MyClass t, JsonGenerator jg, SerializerProvider sp) throws IOException {
jg.writeStartObject();
jg.writeStringField("id", t.getId());
for (Map.Entry<String, Object> entry : t.getProperties().entrySet()) {
jg.writeObjectField(entry.getKey(), entry.getValue());
}
jg.writeEndObject();
}
}
An example using your data :
public class Main {
public static void main(String[] args) throws JsonProcessingException {
MyClass mc = new MyClass();
Map<String, Object> properties = Map.of(
"fruit", "apple",
"color", "red"
);
mc.setId("myid");
mc.setProperties(properties);
System.out.println(mc);
ObjectMapper mapper = new ObjectMapper();
//it will print {"id":"myid","color":"red","fruit":"apple"}
System.out.println(mapper.writeValueAsString(mc));
}
}
The annotation #JsonUnwrapped does exactly what you are looking for.
I use Jackson to serialise POJOs into CSV. Now we need to change the naming for certain fields to snake_case. This is easily done by #JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class).
For compatibility reasons we need some of the renamed fields also with their old name.
E.g.:
public class Pojo {
private int someField;
}
Default will serialise to "someField", SnakeCaseStrategy will serialise to "some_field".
How to get serialization with both?:
{
"someField" : "one",
"some_field" : "one"
}
My first try was a mixin:
public abstract class PojoFormat {
#JsonProperty("someField")
abstract String getSomeField();
}
but this effectively only undoes the naming strategy change.
So how to copy a field in serialization - preferable not by changing the Pojo (this copied fields should be removed when all clients can cope with it).
Little update:
in my real class there some nested class that use JsonUnwrapped and the doc stated that this is not working with custom serializer (didn't know that this makes a difference here).
Well, I have never seen this before, I would be very happy if someone here in this site knows how.
The easy way, in my opinion, is to use a Custom Serializer.
For example:
Using the #JsonSerialize annotation
Register a module
Dynamic Serializer with Reflection
#JsonSerialize annotation
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
#JsonSerializer(using=PojoSerializer.class)
class Pojo {
private String myValue;
// getters and setters
}
class PojoSerializer extends StdSerializer<Pojo> {
public PojoSerializer() {
super(Pojo.class);
}
#Override
public void serialize(Pojo value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeStringField("myValue", value.getMyValue());
gen.writeStringField("my_value", value.getMyValue());
gen.writeEndObject();
}
}
Module
static class Pojo {
private String myValue;
public String getMyValue() {
return myValue;
}
public Pojo setMyValue(String myValue) {
this.myValue = myValue;
return this;
}
}
static class PojoSerializer extends StdSerializer<Pojo> {
public PojoSerializer() {
super(Pojo.class);
}
#Override
public void serialize(Pojo value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeStringField("myValue", value.getMyValue());
gen.writeStringField("my_value", value.getMyValue());
gen.writeEndObject();
}
}
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule("PojoModule");
module.addSerializer(Pojo.class, new PojoSerializer());
mapper.registerModule(module);
final Pojo pojo = new Pojo();
pojo.setMyValue("This is the value of my pojo");
System.out.println(mapper.writeValueAsString(pojo));
}
Reflection
I write some code for you, you might want to see to get new ideias.
This works as a generic way(just to not write several serializers).
// The serializer will be register in the ObjectMapper module.
static class Pojo {
private String myValue = "With snake and camel";
private String value = "Without snake case";
private String thirdValue = "snake & camel";
}
// using the annotation
#JsonSerialize(using = PojoSerializer.class)
static class Pojo2 {
private String pojoName = "Pojo 2";
private String pojo = "pojp";
}
static class PojoSerializer extends StdSerializer<Object> {
public PojoSerializer() {
super(Object.class);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
final Field[] fields = value.getClass().getDeclaredFields();
for(final Field field : fields) {
final String name = field.getName();
final String fieldValue;
try {
// Do not use this!
fieldValue = (String)field.get(value);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
byte firstUpperCase = -1;
for(byte index = 0; index < name.length(); index++) {
final char caractere = name.charAt(index);
// A ascii code is 66 decimal, and 90 is the Z in decimal
if(caractere > 'A' && caractere < 'Z') {
// found the first upper
firstUpperCase = index;
break;
}
}
// writes the normal field name
gen.writeStringField(name, fieldValue);
// if the name is in camel case, we will write in snake case too.
if(firstUpperCase != -1) {
final char lowerLetter = (char)((int) name.charAt(firstUpperCase) + 32);
final String left = name.substring(0, firstUpperCase);
final String right = String.format("%c%s",lowerLetter, name.substring(firstUpperCase + 1));
gen.writeStringField(String.format("%s_%s", left, right), fieldValue);
}
}
gen.writeEndObject();
}
}
You can try to use JsonAnyGetter annotation and define for every POJO extra mapping for backward compatibility.
Let's create a simple interface:
interface CompatibleToVer1 {
#JsonAnyGetter
Map<String, Object> getCompatibilityView();
}
and two classes which implement it:
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
class RootPojo implements CompatibleToVer1 {
private int rootId;
#JsonUnwrapped
private SomePojo pojo;
#Override
public Map<String, Object> getCompatibilityView() {
return Collections.singletonMap("rootId", rootId);
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
class SomePojo implements CompatibleToVer1 {
private int someField;
private String someName;
#Override
public Map<String, Object> getCompatibilityView() {
Map<String, Object> extra = new LinkedHashMap<>();
extra.put("someField", someField);
return extra;
}
}
As you can see, I defined extra columns for each POJO with custom names. Serialising to JSON is straightforward:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
SomePojo pojo = new SomePojo(123, "Tom");
mapper.writeValue(System.out, new RootPojo(1, pojo));
Above code prints:
{
"root_id" : 1,
"some_field" : 123,
"some_name" : "Tom",
"someField" : 123,
"rootId" : 1
}
But for CSV we need to create extra configuration:
CsvMapper csvMapper = CsvMapper.builder().build();
CsvSchema pojoExtraScheme = CsvSchema.builder()
.addColumn("someField")
.build();
CsvSchema rootExtraScheme = CsvSchema.builder()
.addColumn("rootId")
.build();
CsvSchema compatibleSchema = CsvSchema.emptySchema()
.withHeader()
.withColumnsFrom(csvMapper.schemaFor(RootPojo.class))
.withColumnsFrom(rootExtraScheme)
.withColumnsFrom(csvMapper.schemaFor(SomePojo.class))
.withColumnsFrom(pojoExtraScheme);
SomePojo tom = new SomePojo(123, "Tom");
SomePojo jerry = new SomePojo(124, "Jerry");
List<RootPojo> pojos = Arrays.asList(new RootPojo(1, tom), new RootPojo(2, jerry));
ObjectWriter writer = csvMapper.writer(compatibleSchema);
System.out.println(writer.writeValueAsString(pojos));
Above code prints:
some_field,some_name,root_id,rootId,someField
123,Tom,1,1,123
124,Jerry,2,2,124
If you do not want to specify extra columns two times you can implement builder method based on our interface:
CsvSchema createSchemaFor(CompatibleToVer1 entity) {
CsvSchema.Builder builder = CsvSchema.builder();
entity.getCompatibilityView().keySet().forEach(builder::addColumn);
return builder.build();
}
and use as below:
CsvSchema compatibleSchema = CsvSchema.emptySchema()
.withHeader()
.withColumnsFrom(csvMapper.schemaFor(RootPojo.class))
.withColumnsFrom(createSchemaFor(new RootPojo()))
.withColumnsFrom(csvMapper.schemaFor(SomePojo.class))
.withColumnsFrom(createSchemaFor(new SomePojo()));
Using JsonAnyGetter with CSV is really tricky and could be problematic mixing it with other annotations, take a look at: Could please add JsonAnyGetter and JsonAnySetter annotations support?
My lib is calling an API which can return either of the following JSON structure -
{
"key_is_same" : {
"inner" : "val"
}
}
-or-
{
"key_is_same" : [{
"inner" : "val1"
},
{
"inner" : "val2"
}
]
}
Is there any annotation in jakson which can handle this and deserializ it into respective type
Looks like you are looking for the ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature.
Feature that determines whether it is acceptable to coerce non-array (in JSON) values to work with Java collection (arrays, java.util.Collection) types. If enabled, collection deserializers will try to handle non-array values as if they had "implicit" surrounding JSON array. This feature is meant to be used for compatibility/interoperability reasons, to work with packages (such as XML-to-JSON converters) that leave out JSON array in cases where there is just a single element in array.
Feature is disabled by default.
It could be enabled either in ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
Or via the #JsonFormat annotation:
#JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<Foo> oneOrMany;
For illustration purposes, consider the following JSON documents:
{
"oneOrMany": [
{
"value": "one"
},
{
"value": "two"
}
]
}
{
"oneOrMany": {
"value": "one"
}
}
It could be the deserialized to the following classes:
#Data
public class Foo {
private List<Bar> oneOrMany;
}
#Data
public class Bar {
private String value;
}
Just ensure the feature is enabled in your ObjectMapper or your field is annotated with #JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY).
And in case you are looking for the equivalent feature for serialization, refer to WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED.
I would recommend using Object as your data type for the property which is dynamic. So Here is my sample.
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MainObject {
private Object key_is_same;
public Object getKey_is_same() {
return key_is_same;
}
public void setKey_is_same(Object key) {
this.key_is_same = key;
}
public static class KeyObject {
private String inner;
public String getInner() {
return inner;
}
public void setInner(String inner) {
this.inner = inner;
}
}
public static void main(String...s) throws JsonProcessingException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
public static void main(String...s) throws IOException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
// Deserialize
MainObject mainWithObject = om.readValue("{\"key_is_same\":{\"inner\":\"val1\"}}", MainObject.class);
MainObject mainWithList = om.readValue("{\"key_is_same\":[{\"inner\":\"val1\"},{\"inner\":\"val1\"}]}", MainObject.class);
if(mainWithList.getKey_is_same() instanceof java.util.List) {
((java.util.List) mainWithList.getKey_is_same()).forEach(System.out::println);
}
}
}
}
Output
{"key_is_same":{"inner":"val1"}}
{"key_is_same":[{"inner":"val1"},{"inner":"val1"}]}
How to remove the id field ("id" : "urn:jsonschema:org:gradle:Person")
from JSON schema created using Jackson?
Generated Schema
{
"type" : "object",
"id" : "urn:jsonschema:org:gradle:Person",
"properties" : {
"name" : {
"type" : "string"
}
}
}
For POJO class (Person.class)
import com.fasterxml.jackson.annotation.JsonProperty;
public class Person {
#JsonProperty("name")
private String name;
}
Using JSON Schema Generator
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator;
public final class GetJsonSchema {
public static String getJsonSchema2(Class clazz) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonSchemaGenerator jsonSchemaGenerator = new JsonSchemaGenerator(mapper);
JsonSchema jsonSchema = jsonSchemaGenerator.generateSchema(clazz);
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema);
}
}
Invoked like
System.out.println(JsonSchema.Create(Person.class));
Just set id to null.
E.g.:
jsonSchema.setId(null);
As sachin said, jsonSchema.setId(null) is a good way to accomplish your goal. But Venkat is right in that complex types will still have the id's.
One way to remove them is to use a custom SchemaFactoryWrapper, which will instantiate its own visitorContext which will refuse to provide a URN. However, it's important to note this won't work if one type refers to itself (for example, a status object that might have children status objects).
For example:
private static class IgnoreURNSchemaFactoryWrapper extends SchemaFactoryWrapper {
public IgnoreURNSchemaFactoryWrapper() {
this(null, new WrapperFactory());
}
public IgnoreURNSchemaFactoryWrapper(SerializerProvider p) {
this(p, new WrapperFactory());
}
protected IgnoreURNSchemaFactoryWrapper(WrapperFactory wrapperFactory) {
this(null, wrapperFactory);
}
public IgnoreURNSchemaFactoryWrapper(SerializerProvider p, WrapperFactory wrapperFactory) {
super(p, wrapperFactory);
visitorContext = new VisitorContext() {
public String javaTypeToUrn(JavaType jt) {
return null;
}
};
}
}
private static final String printSchema(Class c) {
try {
ObjectMapper mapper = new ObjectMapper();
IgnoreURNSchemaFactoryWrapper visitor = new IgnoreURNSchemaFactoryWrapper();
mapper.acceptJsonFormatVisitor(c, visitor);
JsonSchema schema = visitor.finalSchema();
schema.setId(null);
ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
String asString = writer.writeValueAsString(schema);
return asString;
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
Using the Jackson json library in Java, is it possible to do the following:
public class MyObject {
#JsonRawValue
#JsonUnwrapped
private String rawJson;
}
public class DTO {
public MyObject json;
}
Example of rawJson:
{
"key": "value"
}
When DTO is serialized and rendered as json, I want the JSON to simply look like:
{
"dto": {
"key":"value"
}
}
Instead it's always:
{
"dto":{
"rawJson":{
"key":"value"
}
}
}
Basically it seems that when one of your properties is a JSON value stored in a string with #JsonRawValue, #JsonUnwrapped doesn't work.
Ideally I'm looking for a serialization solution at the level of the MyObject class rather than DTO, or else I'd have to apply the solution to not only DTO but every other DTO where MyObject is referenced.
Update:
The below answer solved this problem in one location, but still is an issue in the following scenario;
public class DTO2 {
#JsonUnwrapped
public MyObjectAbstract json2;
}
Where MyObject is an extension of MyObjectAbstract, and when DTO2 MyObjectAbstract is an instance of MyObject I'd like DTO2 to simply be serialized as:
{
"key": "value"
}
The issue is that whenever DTO2.MyObjectAbstract is an instance of MyObject it's serialized like:
{
"json": {
"key": "value"
}
}
You can use JsonRawValue and JsonValueannotations on a getter method instead. Here is an example:
public class JacksonUnwrapped {
static class MyObject {
private String rawJson;
MyObject(final String rawJson) {
this.rawJson = rawJson;
}
#JsonRawValue
#JsonValue
String getRawJson() {
return rawJson;
}
}
static class DTO {
public MyObject json;
DTO(final MyObject json) {
this.json = json;
}
}
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper objectMapper = new ObjectMapper();
final DTO dto = new DTO(new MyObject(
"{\"key\": \"value\"}"));
System.out.println(objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(dto));
}
}
Output:
{
"json" : {"key": "value"}
}