Spring Boot Openapi composite schema (Inheritance) - java

I have one abstract class and three other subclass. I would like those three subclasses as example on the Swagger/OpenAPI interface.
But the Swagger/OpenApi interface show just the abstract class and the first subclass fields.
OperationRequest
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "subType", visible = true)
#JsonSubTypes({
#JsonSubTypes.Type(value = InstallmentOperationRequest.class, name = "I"),
#JsonSubTypes.Type(value = CreditCardOperationRequest.class, name = "C"),
#JsonSubTypes.Type(value = SingleOperationRequest.class, name = "S")
})
#Schema(
description = "Parent operation request",
discriminatorProperty = "subType",
discriminatorMapping = {
#DiscriminatorMapping(value = "SingleOperation", schema = SingleOperationRequest.class),
#DiscriminatorMapping(value = "InstallmentOperation", schema = InstallmentOperationRequest.class),
#DiscriminatorMapping(value = "CreditCardOperation", schema = CreditCardOperationRequest.class)
})
public abstract class OperationRequest {
private String description;
private OperationTypeEnum type;
private OperationSubTypeEnum subType;
private BigDecimal value;
private String observations;
}
CreditCardOperationRequest
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonTypeName("C")
#Schema(allOf = OperationRequest.class)
public class CreditCardOperationRequest extends OperationRequest {
private String creditCard;
private LocalDate creditCardOperationDate;
private Integer creditCardInstallments;
}
SingleOperationRequest
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonTypeName("S")
#Schema(allOf = OperationRequest.class)
public class SingleOperationRequest extends OperationRequest{
private BigDecimal paidValue;
private YearMonth period;
}
InstallmentOperationRequest
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonTypeName("I")
#Schema(allOf = OperationRequest.class)
public class InstallmentOperationRequest extends OperationRequest{
private Integer installments;
private YearMonth initialInstallment;
}
Json generated
...
"components": {
"schemas": {
"CreditCardOperationRequest": {
"required": [
"creditCard",
"creditCardInstallments",
"description",
"subType",
"type",
"value"
],
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/OperationRequest"
},
{
"type": "object",
"properties": {
"creditCard": {
"type": "string",
"description": "Credit card identification",
"example": "abcd"
},
"creditCardOperationDate": {
"type": "string",
"description": "Credit card operation date",
"format": "date",
"example": "2021-01-10"
},
"creditCardInstallments": {
"minimum": 1,
"type": "integer",
"description": "Number of credit card installments",
"format": "int32",
"example": 5
}
}
}
]
},
"InstallmentOperationRequest": {
"required": [
"description",
"initialInstallment",
"installments",
"subType",
"type",
"value"
],
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/OperationRequest"
},
{
"type": "object",
"properties": {
"installments": {
"minimum": 2,
"type": "integer",
"description": "Number of installments",
"format": "int32",
"example": 5
},
"initialInstallment": {
"type": "string",
"description": "Initial installment period",
"example": "2021-01"
}
}
}
]
},
"OperationRequest": {
"required": [
"description",
"subType",
"type",
"value"
],
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Operation description",
"example": "abc"
},
"type": {
"type": "string",
"description": "Operation type",
"enum": [
"I",
"E"
]
},
"subType": {
"type": "string",
"description": "Operation subType",
"enum": [
"S",
"C",
"I"
]
},
"value": {
"type": "number",
"description": "Operation value",
"format": "double",
"example": 32.56
},
"observations": {
"maxLength": 2147483647,
"minLength": 2,
"type": "string",
"description": "Observations",
"example": "abc"
}
},
"description": "Parent operation request",
"discriminator": {
"propertyName": "subType"
}
},
"SingleOperationRequest": {
"required": [
"description",
"paidValue",
"subType",
"type",
"value"
],
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/OperationRequest"
},
{
"type": "object",
"properties": {
"paidValue": {
"type": "number",
"description": "Operation paid value",
"example": 12.56
},
"period": {
"type": "string",
"description": "Operation period",
"example": "2021-01"
}
}
}
]
}
...
}
}
...

Related

Build JSON from a very complex JSON Schema in Java

I have a complex issue here and some advice or suggestions would be greatly appreciated. Essentially I have a complex JSON schema that looks something like this:
{
"$schema": "http://example.org",
"$id": "http://example.org",
"title": "schema title",
"description": "description",
"properties": {
"name": {
"description": "description",
"type": "string",
"enum": [
"name1",
"name2"
]
},
"storage": {
"description": "description",
"type": "integer",
"minimum": "200",
"maximum": "500",
"default": "200",
},
"domain": {
"description": "description",
"type": "string"
},
},
"if": {
"properties": {
"name": {
"const": "name1"
}
}
},
"then": {
"if": {
"properties": {
"version": {
"const": "version1"
}
}
},
"then": {
"properties": {
"cpus": {
"description": "description",
"type": "integer",
"minimum": 1,
"maximum": 8
},
"memory": {
"description": "description",
"type": "integer",
"minimum": 1,
"maximum": 32
},
},
"required": [
"cpus",
"memory"
]
},
"else": {
"if": {
"properties": {
"version": {
"const": "version2"
}
}
},
"then": {
"properties": {
"cpus": {
"description": "description",
"type": "integer",
"minimum": 1,
"maximum": 8
},
"diskSize": {
"description": "description",
"type": "integer",
"minimum": 250,
"maximum": 1000
},
},
"required": [
"cpus",
"diskSize"
]
}
}
},
"else": {
"if": {
"properties": {
"name": {
"const": "name2"
}
}
},
"then": {
"if": {
"properties": {
"version": {
"const": "version3"
}
}
},
"then": {
"properties": {
"diskSize": {
"description": "description",
"type": "integer",
"minimum": 100,
"maximum": 500
}
"memory": {
"description": "description",
"type": "integer",
"minimum": 1,
"maximum": 28
}
},
"required": [
"diskSize",
"memory"
]
},
"else": {
"if": {
"properties": {
"version": {
"const": "version4"
}
}
},
"then": {
"properties": {
"cpus": {
"description": "description",
"type": "integer",
"minimum": 1,
"maximum": 28
},
"memory": {
"description": "description",
"type": "integer",
"minimum": 1,
"maximum": 64
}
},
"required": [
"cpus",
"memory"
]
}
}
}
}
}
I need to build a JSON object using this schema in java. Every property in the schema is inside of a map that I have access to, so I can quite simply just get the property from the map and add it to a JsonNode object that I am building. Every property under the initial "properties" object is easy to retrieve, I can just get a list of them and then get each one from the map.
The complexity lies in the if/then/else part of the json schema. The only way I can see to find which property I need is to first build the initial part of the json from the first "properties" object and then have some sort of quite complex recursive algorithm that goes into every if/then/else statement and compares the value of the property being evaluated and then returns a list of the properties I need to get from the map. I have looked around online for a library that can build Json from a Json schema in java but haven't found anything that can deal with the complex if/then/else statements.
Any suggestions or ideas would be greatly appreciated.

Elasticsearch:Getting nested object under path is not of nested type

I am new to the Elastic search world.Basically I am trying to retrieve the nested objects based on the ID.This is the JSON representation of my document.
{
"_index": "xyz",
"_type": "abc",
"_id": "12",
"_version": 1,
"found": true,
"_source":
{
"lastModifiedBy": "12",
"lastModifiedDate": "2015-12-31T19:45:29.493Z",
"profile":
[
{
"type": "nested",
"views":
[
{
"type": "nested",
"id": "view1",
"name": "view1",
"properties":
[
{
"name": "default",
"value": false
}
],
"widgets":
[
{
"type": "nested",
"id": "graph",
"name": "graph",
"elementId": "ui_graph",
"properties":
[
{
"name": "currency",
"value": "YEN"
}
]
}
]
}
} ] } ]
I am trying to get the widgets based on the view id.This is the my search query.
"query" : {
"term" : {
"_id" : "12"
}
},
"post_filter" : {
"nested" : {
"query" : {
"filtered" : {
"query" : {
"match_all" : { }
},
"filter" : {
"term" : {
"profile.views.id" : "view1"
}
}
}
},
"path" : "profile.views"
}
}
}
I am not sure what is wrong here.But getting "nested object under path [profile.views] is not of nested type]".
Below is my mapping structure
{
"xyz": {
"mappings": {
"abc": {
"properties": {
"lastModifiedBy": {
"type": "string"
},
"lastModifiedDate": {
"type": "date",
"format": "dateOptionalTime"
},
"name": {
"type": "string"
},
"profile": {
"properties": {
"lastModifiedBy": {
"type": "string"
},
"lastModifiedDate": {
"type": "date",
"format": "dateOptionalTime"
},
"type": {
"type": "string"
},
"views": {
"properties": {
"id": {
"type": "string"
},
"isDefault": {
"type": "boolean"
},
"name": {
"type": "string"
},
"properties": {
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"type": {
"type": "string"
},
"viewId": {
"type": "string"
},
"widgets": {
"properties": {
"elementId": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"properties": {
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"type": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
}
}
Please help!
You are getting the error because you have not specified type as nested for profile and views. Refer to the Docs for how to created nested objects . You should be defining type as nested for every nested object like this
{
"xyz": {
"mappings": {
"abc": {
"properties": {
"lastModifiedBy": {
"type": "string"
},
"lastModifiedDate": {
"type": "date",
"format": "dateOptionalTime"
},
"name": {
"type": "string"
},
"profile": {
"type": "nested", <--- here, you need this for every nested object
"properties": {
"lastModifiedBy": {
"type": "string"
},
"lastModifiedDate": {
"type": "date",
"format": "dateOptionalTime"
},
"type": {
"type": "string"
},
"views": {
"type": "nested",
"properties": {
"id": {
"type": "string"
},
"isDefault": {
"type": "boolean"
},
"name": {
"type": "string"
},
"properties": {
"type": "nested",
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"type": {
"type": "string"
},
"viewId": {
"type": "string"
},
"widgets": {
"type": "nested",
"properties": {
"elementId": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"properties": {
"type": "nested",
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {
"type": "string"
}
}
},
"type": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
}
}
Hope this helps!!

jsonschema2pojo with URL doesn't works

I have problems with using jsonschema2pojo in my code.
So, I use jsonschema2pojo generator(http://www.jsonschema2pojo.org/) to generate POJO's from this URL: http://store.steampowered.com/api/appdetails/?appids=10
Finally, I create all these class in one package 'model'.
Then in I try use it to read json from link but I receive exception:
Exception in thread "main" java.lang.NullPointerException
at controller.View.main(View.java:26)
I don't understand why.
Here is code How I can do it:
public class View {
private static String urlStr = "http://store.steampowered.com/api/appdetails/?appids=10";
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
try {
Game game = objectMapper.readValue(new URL(urlStr), Game.class);
System.out.println(game.getData().getName());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (JsonParseException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
The Game.class is finall class which I set in generator as 'Class name'.
What is wrong? How can I get all these data from this link?
In maven I configure it like that:
<plugin>
<groupId>org.jsonschema2pojo</groupId>
<artifactId>jsonschema2pojo-maven-plugin</artifactId>
<version>0.4.18</version>
<configuration>
<sourceType>jsonschema</sourceType>
<outputEncoding>${project.build.sourceEncoding}</outputEncoding>
<outputDirectory>${project.build.directory}/generated-sources</outputDirectory>
<annotationStyle>jackson2</annotationStyle>
<generateBuilders>true</generateBuilders>
<initializeCollections>true</initializeCollections>
</configuration>
<executions>
<execution>
<id>generate-game</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<targetPackage>model.game</targetPackage>
<sourceDirectory>${basedir}/src/main/resources/schema/game</sourceDirectory>
</configuration>
</execution>
</executions>
</plugin>
As I said, all classes are in src/main/java and package 'model', my schema is in sr/main/resources/schema/ in file 'game'
And my schema looks like:
{
"type": "object",
"required":false,
"javaType":"model.Game",
"properties": {
"10": {
"id": "10",
"type": "object",
"properties": {
"success": {
"id": "success",
"type": "boolean"
},
"data": {
"id": "data",
"type": "object",
"properties": {
"type": {
"id": "type",
"type": "string"
},
"name": {
"id": "name",
"type": "string"
},
"steam_appid": {
"id": "steam_appid",
"type": "integer"
},
"required_age": {
"id": "required_age",
"type": "integer"
},
"is_free": {
"id": "is_free",
"type": "boolean"
},
"detailed_description": {
"id": "detailed_description",
"type": "string"
},
"about_the_game": {
"id": "about_the_game",
"type": "string"
},
"supported_languages": {
"id": "supported_languages",
"type": "string"
},
"header_image": {
"id": "header_image",
"type": "string"
},
"website": {
"id": "website",
"type": "null"
},
"pc_requirements": {
"id": "pc_requirements",
"type": "object",
"properties": {
"minimum": {
"id": "minimum",
"type": "string"
}
}
},
"mac_requirements": {
"id": "mac_requirements",
"type": "object",
"properties": {
"minimum": {
"id": "minimum",
"type": "string"
}
}
},
"linux_requirements": {
"id": "linux_requirements",
"type": "object",
"properties": {
"minimum": {
"id": "minimum",
"type": "string"
}
}
},
"developers": {
"id": "developers",
"type": "array",
"items": {
"id": "0",
"type": "string"
}
},
"publishers": {
"id": "publishers",
"type": "array",
"items": {
"id": "0",
"type": "string"
}
},
"price_overview": {
"id": "price_overview",
"type": "object",
"properties": {
"currency": {
"id": "currency",
"type": "string"
},
"initial": {
"id": "initial",
"type": "integer"
},
"final": {
"id": "final",
"type": "integer"
},
"discount_percent": {
"id": "discount_percent",
"type": "integer"
}
}
},
"packages": {
"id": "packages",
"type": "array",
"items": [
{
"id": "0",
"type": "string"
},
{
"id": "1",
"type": "integer"
},
{
"id": "2",
"type": "integer"
},
{
"id": "3",
"type": "integer"
}
]
},
"package_groups": {
"id": "package_groups",
"type": "array",
"items": {
"id": "0",
"type": "object",
"properties": {
"name": {
"id": "name",
"type": "string"
},
"title": {
"id": "title",
"type": "string"
},
"description": {
"id": "description",
"type": "string"
},
"selection_text": {
"id": "selection_text",
"type": "string"
},
"save_text": {
"id": "save_text",
"type": "string"
},
"display_type": {
"id": "display_type",
"type": "integer"
},
"is_recurring_subscription": {
"id": "is_recurring_subscription",
"type": "string"
},
"subs": {
"id": "subs",
"type": "array",
"items": [
{
"id": "0",
"type": "object",
"properties": {
"packageid": {
"id": "packageid",
"type": "string"
},
"percent_savings_text": {
"id": "percent_savings_text",
"type": "string"
},
"percent_savings": {
"id": "percent_savings",
"type": "integer"
},
"option_text": {
"id": "option_text",
"type": "string"
},
"option_description": {
"id": "option_description",
"type": "string"
},
"can_get_free_license": {
"id": "can_get_free_license",
"type": "string"
},
"is_free_license": {
"id": "is_free_license",
"type": "boolean"
},
"price_in_cents_with_discount": {
"id": "price_in_cents_with_discount",
"type": "integer"
}
}
},
{
"id": "1",
"type": "object",
"properties": {
"packageid": {
"id": "packageid",
"type": "integer"
},
"percent_savings_text": {
"id": "percent_savings_text",
"type": "string"
},
"percent_savings": {
"id": "percent_savings",
"type": "integer"
},
"option_text": {
"id": "option_text",
"type": "string"
},
"option_description": {
"id": "option_description",
"type": "string"
},
"can_get_free_license": {
"id": "can_get_free_license",
"type": "string"
},
"is_free_license": {
"id": "is_free_license",
"type": "boolean"
},
"price_in_cents_with_discount": {
"id": "price_in_cents_with_discount",
"type": "integer"
}
}
},
{
"id": "2",
"type": "object",
"properties": {
"packageid": {
"id": "packageid",
"type": "integer"
},
"percent_savings_text": {
"id": "percent_savings_text",
"type": "string"
},
"percent_savings": {
"id": "percent_savings",
"type": "integer"
},
"option_text": {
"id": "option_text",
"type": "string"
},
"option_description": {
"id": "option_description",
"type": "string"
},
"can_get_free_license": {
"id": "can_get_free_license",
"type": "string"
},
"is_free_license": {
"id": "is_free_license",
"type": "boolean"
},
"price_in_cents_with_discount": {
"id": "price_in_cents_with_discount",
"type": "integer"
}
}
},
{
"id": "3",
"type": "object",
"properties": {
"packageid": {
"id": "packageid",
"type": "integer"
},
"percent_savings_text": {
"id": "percent_savings_text",
"type": "string"
},
"percent_savings": {
"id": "percent_savings",
"type": "integer"
},
"option_text": {
"id": "option_text",
"type": "string"
},
"option_description": {
"id": "option_description",
"type": "string"
},
"can_get_free_license": {
"id": "can_get_free_license",
"type": "string"
},
"is_free_license": {
"id": "is_free_license",
"type": "boolean"
},
"price_in_cents_with_discount": {
"id": "price_in_cents_with_discount",
"type": "integer"
}
}
}
]
}
}
}
},
"platforms": {
"id": "platforms",
"type": "object",
"properties": {
"windows": {
"id": "windows",
"type": "boolean"
},
"mac": {
"id": "mac",
"type": "boolean"
},
"linux": {
"id": "linux",
"type": "boolean"
}
}
},
"metacritic": {
"id": "metacritic",
"type": "object",
"properties": {
"score": {
"id": "score",
"type": "integer"
},
"url": {
"id": "url",
"type": "string"
}
}
},
"categories": {
"id": "categories",
"type": "array",
"items": [
{
"id": "0",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"description": {
"id": "description",
"type": "string"
}
}
},
{
"id": "1",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"description": {
"id": "description",
"type": "string"
}
}
}
]
},
"genres": {
"id": "genres",
"type": "array",
"items": {
"id": "0",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "string"
},
"description": {
"id": "description",
"type": "string"
}
}
}
},
"screenshots": {
"id": "screenshots",
"type": "array",
"items": [
{
"id": "0",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
},
{
"id": "1",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
},
{
"id": "2",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
},
{
"id": "3",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
},
{
"id": "4",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
},
{
"id": "5",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
},
{
"id": "6",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
},
{
"id": "7",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
},
{
"id": "8",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
},
{
"id": "9",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
},
{
"id": "10",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
},
{
"id": "11",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
},
{
"id": "12",
"type": "object",
"properties": {
"id": {
"id": "id",
"type": "integer"
},
"path_thumbnail": {
"id": "path_thumbnail",
"type": "string"
},
"path_full": {
"id": "path_full",
"type": "string"
}
}
}
]
},
"recommendations": {
"id": "recommendations",
"type": "object",
"properties": {
"total": {
"id": "total",
"type": "integer"
}
}
},
"achievements": {
"id": "achievements",
"type": "object",
"properties": {
"total": {
"id": "total",
"type": "integer"
}
}
},
"release_date": {
"id": "release_date",
"type": "object",
"properties": {
"coming_soon": {
"id": "coming_soon",
"type": "boolean"
},
"date": {
"id": "date",
"type": "string"
}
}
},
"support_info": {
"id": "support_info",
"type": "object",
"properties": {
"url": {
"id": "url",
"type": "string"
},
"email": {
"id": "email",
"type": "string"
}
}
},
"background": {
"id": "background",
"type": "string"
}
}
}
},
"required": [
"success",
"data",
"id",
"path_thumbnail",
"path_full"
]
}
},
"required": [
"10"
]
}
With this config I can read all data using this line instead of before System.out.println:
System.out.println( objectMapper.writeValueAsString( game ) );
Now I can see full json string, but how can I read it using 'game.getData().getName()' etc ?
The JSON schema that you gave defines "10" as a top-level property containing everything else.
{
"type": "object",
"required":false,
"javaType":"model.Game",
"properties": {
"10": {
"id": "10",
"type": "object",
"properties": {
"success": {
"id": "success",
"type": "boolean"
},
"data": {
"id": "data",
"type": "object",
"properties": {
"type": {
"id": "type",
"type": "string"
},
"name": {
"id": "name",
"type": "string"
},
...
jsonschema2pojo reads that schema and generates a class named _10 as part of the model. You can find this in _10.java in the generated-sources directory.
#JsonInclude(JsonInclude.Include.NON_NULL)
#Generated("org.jsonschema2pojo")
#JsonPropertyOrder({
"success",
"data"
})
public class _10 {
I don't know if this was intentional in the schema. If it's intentional, then you would need to access the name like this.
System.out.println(game.get10().getData().getName());
I was able to get it working after making that change.
EDIT: It appears these JSON messages are expected to have a variable ID as a top-level key containing everything else, so we need to handle this a bit differently. We can generalize the schema so that it isn't specific to a particular ID, and then we can use a two-step process to parse the message. First, we'll use [ObjectMapper#readValue](https://fasterxml.github.io/jackson-databind/javadoc/2.5/com/fasterxml/jackson/databind/ObjectMapper.html#readValue(java.net.URL, com.fasterxml.jackson.core.type.TypeReference)) to parse the whole message into a Map<String, Object>. We expect that top-level map will contain a single element, corresponding to the ID of the document we requested, and its value will be a sub-map containing the values we're interested in. We'll then pass that sub-map through [ObjectMapper#convertValue](https://fasterxml.github.io/jackson-databind/javadoc/2.5/com/fasterxml/jackson/databind/ObjectMapper.html#convertValue(java.lang.Object, java.lang.Class)) to get the Game object that we want.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cnauroth</groupId>
<artifactId>test-json</artifactId>
<packaging>jar</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>Test JSON</name>
<description>Test JSON</description>
<build>
<plugins>
<plugin>
<groupId>org.jsonschema2pojo</groupId>
<artifactId>jsonschema2pojo-maven-plugin</artifactId>
<version>0.4.18</version>
<configuration>
<sourceType>jsonschema</sourceType>
<outputEncoding>${project.build.sourceEncoding}</outputEncoding>
<outputDirectory>${project.build.directory}/generated-sources/java</outputDirectory>
<annotationStyle>jackson2</annotationStyle>
<generateBuilders>true</generateBuilders>
<initializeCollections>true</initializeCollections>
</configuration>
<executions>
<execution>
<id>generate-game</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<targetPackage>model.game</targetPackage>
<sourceDirectory>${basedir}/src/main/resources/schema/game</sourceDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<mainClass>View</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<finalName>${project.artifactId}</finalName>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
</project>
game
The JSON schema file is too large to paste, so here is a link to a gist.
View.java
import java.net.URI;
import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import model.Game;
public class View {
public static void main(String[] args) throws Exception {
String id = args[0];
URI uri = new URI("http", "store.steampowered.com", "/api/appdetails",
"appids=" + id, null);
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> obj = objectMapper.readValue(uri.toURL(),
new TypeReference<Map<String, Object>>(){});
Game game = objectMapper.convertValue(obj.get(id), Game.class);
System.out.println(game.getData().getName());
}
}
Demo
> mvn clean package
> java -jar target/test-json.jar 10
Counter-Strike
> java -jar target/test-json.jar 219
Half-Life 2: Demo
Additionally, I needed to make one other change to the schema. I removed the linux_requirements property. This is because looking at document 10 vs. document 219, it's an object in the former and it's an array in the latter. For document 219, the parser didn't know how to make sense of the array, because we were expecting an object. It's odd for data returned from an API to be inconsistent like this. You might need to do some more tinkering with the schema to get this fully working for all possible JSON documents returned from that API.
I have put all of the code into a GitHub repository. You can take that code, build it and run it to see it working. Then, you can adapt it further to your needs.
Instead of trying to model the object containing the ids with properties, use additionalProperties. Assuming that the top level type contains several games, you could try replacing
{
"type": "object",
"required":false,
"javaType":"model.Game",
"properties": {
"10": {
"id": "10",
"type": "object",
...
}
}
}
with something like
{
"type": "object",
"required":false,
"javaType":"model.Games",
"additionalProperties": {
"id": "game",
"type": "object",
"javaType": "model.Game"
...
}
}
This should make a model.Games type with a method like public Map<String, Game> getAdditionalProperties().

AVRO GenericDatumWriter Fails when writing a ComplexType datum

Here is my scenario:
I have a schema for my class that is validated by Avro:
{
"type": "record",
"name": "MyCLass",
"namespace": "com.somepackage",
"fields": [
{
"name": "attributes",
"type": {
"type": "array",
"items": {
"type": "record",
"name": "KeyValuePair",
"fields": [
{
"name": "key",
"type": "string"
},
{
"name": "value",
"type": "string"
}
]
},
"java-class": "java.util.List"
}
},
{
"name": "someString",
"type": "string"
},
{
"name": "myclass1",
"type": {
"type": "record",
"name": "MyClass1",
"fields": [
{
"name": "attributes",
"type": {
"type": "record",
"name": "ArrayOfKeyValuePair",
"fields": [
{
"name": "item",
"type": {
"type": "array",
"items": "KeyValuePair",
"java-class": "java.util.List"
}
}
]
}
},
{
"name": "labels",
"type": {
"type": "record",
"name": "ArrayOfXsdString",
"fields": [
{
"name": "item",
"type": {
"type": "array",
"items": "string",
"java-class": "java.util.List"
}
}
]
}
},
{
"name": "uniqueID",
"type": "string"
}
]
}
},
{
"name": "MyClass2",
"type": {
"type": "record",
"name": "MyClass2",
"fields": [
{
"name": "attributes",
"type": "ArrayOfKeyValuePair"
},
{
"name": "someString",
"type": "string"
},
{
"name": "someLabel",
"type": "ArrayOfXsdString"
},
{
"name": "someId",
"type": "string"
}
]
}
}
]
}
This schema was generated with `GenericRecord obj = new GenericData.Record(ReflectData.get().getSchema(MyClass.class));
next I build the list of 4 parameters that the obj expects in it's values list:
KeyValuePair kv1 = new KeyValuePair();
kv1.setKey("key1");
kv1.setValue("val1");
KeyValuePair kv2 = new KeyValuePair();
kv2.setKey("key2");
kv2.setValue("val2");
List<KeyValuePair> attr = new ArrayList<KeyValuePair>();
attr.add(kv1);
attr.add(kv2);
ArrayOfKeyValuePair arrKV = new ArrayOfKeyValuePair();
arrKV.getItem().add(kv1);
MyClass1 ud = new MyClass1();
ud.setAttributes(arrKV);
ud.setUniqueID("SomeID");
MyCLass2 sd = new MyCLass2();
sd.setAttributes(arrKV);
sd.setExternalCaseID("SomeID");
sd.setUniqueID("SomeId");
obj.put("attributes", attr);
obj.put("workFlowName", "Nume workflow");
obj.put("userDescriptor", ud);
obj.put("subscriberDescriptor", sd);
And when I try:
ByteArrayOutputStream out = new ByteArrayOutputStream();
DatumWriter<GenericRecord> writerS = new GenericDatumWriter<GenericRecord>(schemaMentionedAbove);
Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
writerS.write(obj, encoder);
encoder.flush();
out.close();
The code fails at line: writerS.write(obj, encoder); with error:
KeyValuePair cannot be cast to org.apache.avro.generic.IndexedRecord
KeyValuePair class is a simple class with 2 fields: String key, String value.
After debugging I can see that DatumWriter fails on iterating through the first "attributes" array of records.
Any help appreciated! Thanks

Setting a dynamic date format in Elastic Search

I am new to Elastic Search.
I have a User mapping and associated with the User is a Nested Object extraDataValues. In this object is the id, a string value and another nested object. For example:
"extraDataValues": [
{
"id": 1,
"value": "01/01/2016 00:00:00",
"id": 10,
"label": "Metadata Date",
"displayable": true
},
},
{
"id": 2,
"value": "aaaa",
"id": 11,
"label": "Metadata TextBox",
"displayable": true
},
}
],
As you can see, value field can be a date or a normal string. The problem arises here, I want to be able to sort this value given that it could be either a date or a normal string. Moreover, the date can be in two formats: "dd/MM/yyyy HH:mm:ss", "dd/MM/yyyy". How can I achieve this firstly with Elastic Search (so I can understand the theory) and then Java?
I have tried adding "dynamic_date_formats" : ["dd/MM/yyyy HH:mm:ss", "dd/MM/yyyy"]
to no avail.
The mapping for the Users is:
User Mapping Document
{
"User": {
"properties": {
"fullName": {
"type": "string",
"index": "not_analyzed",
"fields": {
"raw_lower_case": {
"type": "string",
"analyzer": "case_insensitive"
}
}
},
"username": {
"type": "string",
"index": "not_analyzed",
"fields": {
"raw_lower_case": {
"type": "string",
"analyzer": "case_insensitive"
}
}
},
"email": {
"type": "string",
"index": "not_analyzed",
"fields": {
"raw_lower_case": {
"type": "string",
"analyzer": "case_insensitive"
}
}
},
"firstName": {
"type": "string",
"index": "not_analyzed",
"fields": {
"raw_lower_case": {
"type": "string",
"analyzer": "case_insensitive"
}
}
},
"surname": {
"type": "string",
"index": "not_analyzed",
"fields": {
"raw_lower_case": {
"type": "string",
"analyzer": "case_insensitive"
}
}
},
"id": {
"type": "long"
},
"extraDataValues": {
"type": "nested",
"dynamic_date_formats" : ["dd/MM/yyyy HH:mm:ss", "dd/MM/yyyy"],
"properties": {
"extraDataValueObject": {
"properties": {
"id": {
"type": "long"
},
"label": {
"type": "string"
},
"displayable": {
"type": "boolean"
}
}
},
"value": {
"type": "string",
"index": "not_analyzed",
"fields": {
"raw_lower_case": {
"type": "string",
"analyzer": "case_insensitive"
}
}
}
}
}
}
}
}
You can't do that the way you are trying to do it. dynamic_date_formats are used only for dynamically added date fields, not for date fields that you specify in your mapping (from the documentation).
What I would suggest trying out is this mapping:
"value": {
"type": "string",
"fields": {
"date1": {
"type": "date",
"format": "dd/MM/yyyy HH:mm:ss",
"ignore_malformed": "true"
},
"date2": {
"type": "date",
"format": "dd/MM/yyyy",
"ignore_malformed": "true"
}
}
}
Where you have a field which is string (for the string type part of the value) and for it you define two subfields each with a different date format. It's imperative to have for them "ignore_malformed": "true" in case you really have a string instead of a date coming in.
In this way you can index this:
POST /my_index/user/1
{
"value": "aaa"
}
POST /my_index/user/2
{
"value": "01/01/2016 00:00:00"
}
POST /my_index/user/3
{
"value": "02/02/2016"
}
And you could differentiate between which type of date or string was indexed like this in a query:
"query": {
"filtered": {
"filter": {
"exists": {
"field": "value.date2"
}
}
}
}
If ES was able to index something under value.date2 then you get that document back. The same goes for value.date1, of course.

Categories