Java Client ingesting Object Fields

Hello,

I am unable to ingest content in an object field with the java client 2.2.0.
Please note that i have the same problem for nested objects but for String/long/arrays etc, it all works

Field creation

mappingSource
        .startObject(ElasticLibrary.TYPE_CONTENT)
            .startObject("properties")
                .startObject("part")
                    .field(TYPE, TYPE_STRING)
                .endObject()
                .startObject("name")
                    .field(TYPE, TYPE_STRING)
                .endObject()
            .endObject()
        .endObject();

The mapping is created fine.

This is how i try to ingest :

contentBuilder = XContentFactory.jsonBuilder();
contentBuilder.startObject().prettyPrint();

contentBuilder.field(ElasticLibrary.TYPE_CONTENT, new Content(text));

contentBuilder.endObject().prettyPrint().humanReadable(true);

indexRequestBuilder.setSource(contentBuilder);
indexRequestBuilder.execute().actionGet();

My content object :

public class Content implements Serializable{

private String part;
private String name;

If i run this i will get the error

org.elasticsearch.index.mapper.MapperParsingException: object mapping for [CONTENT] tried to parse field [CONTENT] as object, but found a concrete value

Does anyone know why i am getting this error ?

Thank you.

Looks to me like you're mapping a type, "content", with string properties "part" and "name", but then constructing an instance of Content by passing a single parameter called "text". Don't know what the single arg c'tor of your Content class does. Also don't know what the expected values for content.part and content.name would be, if all you have is "text".

I think what you want to do is something like

contentBuilder = XContentFactory.jsonBuilder()
  .startObject().prettyPrint()
    .startObject("content").field("part", part).field("name", name).endObject()
  .endObject().prettyPrint().humanReadable(true);
indexRequestBuilder.setSource(contentBuilder);
indexRequestBuilder.execute().actionGet();

but, again, I can't tell from where the values for "part" and "name" should come.

If i remove the type specification i get the following error

Caused by: MapperParsingException[No type specified for field [name]]

Here is a sample application that will reproduce the problem

private static String INDICE = "test";
private static String DOCUMENT_TYPE = "doc";
private static String FIELD_NAME = "content";
		
public static void main(String[] args) throws IOException {

	Builder builder = Settings.builder().put("cluster.name", "elastic");
	TransportClient transportClient = TransportClient.builder().settings(builder).build();
	transportClient.addTransportAddress(new InetSocketTransportAddress(Inet4Address.getByName("localhost"), 9300));
	Client client = (Client) transportClient;

	IndicesAdminClient indicesAdminClient = client.admin().indices();
	IndicesExistsRequest indicesExistsRequest = new IndicesExistsRequest(INDICE);
	IndicesExistsResponse indicesExistsResponse = indicesAdminClient.exists(indicesExistsRequest).actionGet();

	if (!indicesExistsResponse.isExists()) {

		CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices()
				.prepareCreate(INDICE).addMapping(DOCUMENT_TYPE, getDefaultMapping());

		createIndexRequestBuilder.execute().actionGet();

	}

	IndexRequestBuilder indexRequestBuilder = client.prepareIndex(INDICE, DOCUMENT_TYPE, UUID.randomUUID().toString());
	XContentBuilder contentBuilder = null;

	contentBuilder = XContentFactory.jsonBuilder();
	contentBuilder.startObject().prettyPrint();

	contentBuilder.field(FIELD_NAME, new Content("This is my name", "This is the part"));

	contentBuilder.endObject().prettyPrint().humanReadable(true);

	indexRequestBuilder.setSource(contentBuilder);
	indexRequestBuilder.execute().actionGet();
}

private static XContentBuilder getDefaultMapping() throws IOException {

	XContentBuilder mappingSource = XContentFactory.jsonBuilder();

    mappingSource.startObject().startObject("properties");
    
    mappingSource
    .startObject(FIELD_NAME)
        .startObject("properties")
            .startObject("part")
                .field("type", "String")
            .endObject()
            .startObject("name")
                .field("type", "String")
            .endObject()
        .endObject()
    .endObject();

    mappingSource.endObject().endObject().prettyPrint();

    return mappingSource;

}

And the content object

public class Content {

String name;
String part;

public Content(String name, String part) {
	this.name = name;
	this.part = part;
}

public String getName() {
	return name;
}

public void setName(String name) {
	this.name = name;
}

public String getPart() {
	return part;
}

public void setPart(String part) {
	this.part = part;
}

}

A quick look at the request shows that the content is

index {[test][doc][e04cc5f2-9b62-4543-94e5-25d0e6dc54b8], source[{
"content" : "org.elastic.elasticnested.Content@191cb49e"
}]}

So i decided to try to convert to JSON before passing it to the builder, this is the request

index {[test][doc][b6931a80-045f-4527-a0ca-b0bcf7d5925f], source[{
"content" : "{"name":"This is my name","part":"This is the part"}"
}]}

It still considered the content as a String.

Found it by creating a map

Map<String, Object> map = new HashMap<>();
	
map.put("name", "This is my name");
map.put("part", "This is the part");
	
contentBuilder.field(FIELD_NAME, map);

Result

{
  "took": 10,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1,
    "hits": [
      {
        "_index": "test",
        "_type": "doc",
        "_id": "4b96664b-146a-45ad-99b6-e8e229d8de7c",
        "_score": 1,
        "_source": {
          "content": {
            "name": "This is my name",
            "part": "This is the part"
          }
        }
      }
    ]
  }
} 

Is there not a way to do this by passing the object directly ?

I personally use Jackson for this.

https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-docs-index.html#java-docs-index-generate-beans

I don't understand why you can't just use the startObject helper as I showed.