1

I write app in Spring.

This is my json: (it is an array of json objects)

[{"id" : 643419352,
  "status" : "removed_by_user",
  "url" : "https://www.olx.pl/d/oferta/opona-12-1-2-x-2-1-4-etrto-62-203-detka-CID767-IDHxILu.html",
  "created_at" : "2020-11-27 10:46:07",
  "activated_at" : "2020-12-11 12:41:12",
  "valid_to" : "2020-12-17 15:38:10",
  "title" : "opona 12 1/2 \" x 2 1/4 etrto 62-203 + dętka",
  "description" : "opona w bardzo dobrym stanie + dętka, rozmiar 12 1/2 x 2 1/4 , dętka z zaworem samochodowym",
  "category_id" : 1655,
  "advertiser_type" : "private",
  "external_id" : null,
  "external_url" : null,
  "contact" : {
    "name" : "Damazy",
    "phone" : "501474399"
  },
  "location" : {
    "city_id" : 10609,
    "district_id" : 301,
    "latitude" : "51.80178",
    "longitude" : "19.43928"
  },
  "images" : [ {
    "url" : "https://ireland.apollo.olxcdn.com:443/v1/files/efa9any4ryrb-PL/image;s=1000x700"
  } ],
  "price" : {
    "value" : "9",
    "currency" : "PLN",
    "negotiable" : false,
    "budget" : false,
    "trade" : false
  },
  "salary" : null,
  "attributes" : [ {
    "code" : "state",
    "value" : "used",
    "values" : null
  } ],
  "courier" : null
}, {
  "id" : 643435839,
  "status" : "removed_by_user",
  "url" : "https://www.olx.pl/d/oferta/opona-4-80-4-00-8-do-taczki-nowa-CID628-IDHxN3p.html",
  "created_at" : "2020-11-27 11:53:47",
  "activated_at" : "2020-11-27 11:54:36",
  "valid_to" : "2020-12-17 15:38:07",
  "title" : "opona 4.80/4.00 - 8 do taczki nowa!!!",
  "description" : "opona do taczki, nowa, nigdy nie używana, stan idealny.\r\nrozmiar 4.80/4.00-8. \r\nopona do taczki, nowa, nigdy nie używana, stan idealny.\r\nrozmiar 4.80/4.00-8.",
  "category_id" : 1636,
  "advertiser_type" : "private",
  "external_id" : null,
  "external_url" : null,
  "contact" : {
    "name" : "Damazy",
    "phone" : "501474399"
  },
  "location" : {
    "city_id" : 10609,
    "district_id" : 301,
    "latitude" : "51.80178",
    "longitude" : "19.43928"
  },
  "images" : [ {
    "url" : "https://ireland.apollo.olxcdn.com:443/v1/files/qmvssagjnq1r2-PL/image;s=1000x700"
  } ],
  "price" : {
    "value" : "9",
    "currency" : "PLN",
    "negotiable" : false,
    "budget" : false,
    "trade" : false
  },
  "salary" : null,
  "attributes" : [ {
    "code" : "state",
    "value" : "new",
    "values" : null
  } ],
  "courier" : null
}]

and this is my entity class Advert :

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Entity
public class Advert {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long ident;
    private int id;
    private String status;
    private String url;
    private String created_at;
    private String activated_at;
    private String valid_to;
    private String title;
    @Lob
    private String description;
    private int category_id;
    private String advertiser_type;
    private Long external_id;
    private String external_url;
    private String salary;
    private String attributes;
    private String courier;

    @Embedded
    private Location location;

    @Embedded
    private Contact contact;

    @Embedded
    private Price price;

    private String images;

and my saveAdverts method :

@RequestMapping("/saveadverts")
    public String saveAdverts() throws IOException {
        HttpEntity<String> requestEntity = entity.requestEntityProvider();
        String url = "https://www.olx.pl/api/partner/adverts";
        ResponseEntity<JsonNode> responseEntity = template.exchange(url, HttpMethod.GET, requestEntity, JsonNode.class);
        String adverts = responseEntity.getBody().get("data").toString();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        try {
            Advert[] array = objectMapper.readValue(adverts, Advert[].class);
            for(Advert a : array) {
                advertRepository.save(a);
            }
        } catch (Exception e) {
            System.out.println(e);
        }
        return "index";
    }

what I want to do is to parse json to entity objects and save all adverts objects to sql database in one table. Method execution stops with exception on this line :

Advert[] array = objectMapper.readValue(adverts, Advert[].class);

I get this error message :

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.lang.String from Array value (token JsonToken.START_ARRAY) at [Source: (StringReader); line: 24, column: 14] (through reference chain: java.lang.Object[][0]->pl.vida.model.Advert["images"])

Please notice that the field "images" realtes to nested array of json objects. Please help, I spent one week on this and no result. Thanks

2
  • Just a few questions: 1. Are you using Hibernate or Spring Data JPA to store the data? 2. Your API is working fine? I mean are you getting API data inside String adverts=...? 3. Which database are you using? Commented Aug 10, 2021 at 15:17
  • Hello, I am using Hibernate, Yes I have data inside variable adverts. Problem is with parsing to object Commented Aug 10, 2021 at 19:23

1 Answer 1

1

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.lang.String from Array value (token JsonToken.START_ARRAY) at [Source: (StringReader); line: 24, column: 14] (through reference chain: java.lang.Object[][0]->pl.vida.model.Advert["images"])

You are getting the above exception because you are trying to convert an array of objects into a String which is not possible. See in your JSON images & attributes are array of objects.

"images": [{ "url": "https://ireland.apollo.olxcdn.com:443/v1/files/qmvssagjnq1r2-PL/image;s=1000x700" }],
"attributes": [{ "code": "state", "value": "new", "values": null }]

and in your Advert class you have created images & attributes as String types.

private String attributes;
private String images;

Generally, for array kind of object, we make fields either List or Set and if the field is List/Set then we need to create separate classes for them and map as OneToMany relationship. So creating separate classes means separate tables will be created but you don't want to have multiple tables. You want to store all the data in a single table. In a normal case, it is not possible but if we write some additional configuration classes then we can achieve your requirement. These tweaks have been provided by Hibernate itself.

So basically, Hibernate has provided some built-in types like String, Integer, Float, Date, Timezone, etc. Here you can check the complete list of built-in types. But according to our requirements, we can create custom types as well. So to store the array kind of data Hibernate didn't provide any built-in type. Hence we shall create a custom type.

Solution: So we want to store an array of object data and we can easily store it in com.fasterxml.jackson.databind.JsonNode object. But Hibernate doesn't support this class as a field type. Hence to make supportable for this class we need to write 2 extra classes i.e. JsonNodeStringType.java & JsonNodeStringDescriptor.

JsonNodeStringType.java

public class JsonNodeStringType extends AbstractSingleColumnStandardBasicType<JsonNode> implements DiscriminatorType<JsonNode> {
    public static final JsonNodeStringType INSTANCE = new JsonNodeStringType();
    public JsonNodeStringType() {
        super(VarcharTypeDescriptor.INSTANCE, JsonNodeStringDescriptor.INSTANCE);
    }

    @Override
    public String getName() {
        return "JsonNode";
    }

    @Override
    public JsonNode stringToObject(String xml) {
        return fromString(xml);
    }

    @Override
    public String objectToSQLString(JsonNode value, Dialect dialect) {
        return '\'' + toString(value) + '\'';
    }
}

JsonNodeStringDescriptor.java

public class JsonNodeStringDescriptor extends AbstractTypeDescriptor<JsonNode> {
    public static final ObjectMapper mapper = new ObjectMapper();
    public static final JsonNodeStringDescriptor INSTANCE = new JsonNodeStringDescriptor();

    public JsonNodeStringDescriptor() {
        super(JsonNode.class, ImmutableMutabilityPlan.INSTANCE);
    }

    @Override
    public String toString(JsonNode value) {
        try {
            return mapper.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public JsonNode fromString(String string) {
        try {
            return mapper.readTree(string);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <X> X unwrap(JsonNode value, Class<X> type, WrapperOptions options) {
        if (value == null) {
            return null;
        }
        if (String.class.isAssignableFrom(type)) {
            return (X) toString(value);
        }
        throw unknownUnwrap(type);
    }

    @Override
    public <X> JsonNode wrap(X value, WrapperOptions options) {
        if (value == null) {
            return null;
        }
        if (String.class.isInstance(value)) {
            return fromString(value.toString());
        }
        throw unknownWrap(value.getClass());
    }
}

Now our Advert class will look like as

import org.hibernate.annotations.Type;
@Setter
@Getter
@ToString
@Entity
@Table(name = "advert")
@TypeDef(name = "JsonNode", typeClass = JsonNodeStringType.class)
public class Advert {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ident", unique = true, nullable = false)
    private Long ident;
    private int id;
    private String status;
    private String url;
    private String created_at;
    private String activated_at;
    private String valid_to;
    private String title;
    @Lob
    private String description;
    private int category_id;
    private String advertiser_type;
    private Long external_id;
    private String external_url;
    private String salary;
    private String courier;
    @Embedded
    private Location location;
    @Embedded
    private Contact contact;
    @Embedded
    private Price price;
    @Type(type = "JsonNode")
    private JsonNode images;
    @Type(type = "JsonNode")
    private JsonNode attributes;
}

Here we go. If you execute the below code it works perfectly.

String advertsString = "[ { \"id\": 643419352, \"status\": \"removed_by_user\", \"url\": \"https://www.olx.pl/d/oferta/opona-12-1-2-x-2-1-4-etrto-62-203-detka-CID767-IDHxILu.html\", \"created_at\": \"2020-11-27 10:46:07\", \"activated_at\": \"2020-12-11 12:41:12\", \"valid_to\": \"2020-12-17 15:38:10\", \"title\": \"opona 12 1/2 \\\" x 2 1/4 etrto 62-203 + dętka\", \"description\": \"opona w bardzo dobrym stanie + dętka, rozmiar 12 1/2 x 2 1/4 , dętka z zaworem samochodowym\", \"category_id\": 1655, \"advertiser_type\": \"private\", \"external_id\": null, \"external_url\": null, \"contact\": { \"name\": \"Damazy\", \"phone\": \"501474399\" }, \"location\": { \"city_id\": 10609, \"district_id\": 301, \"latitude\": \"51.80178\", \"longitude\": \"19.43928\" }, \"images\": [ { \"url\": \"https://ireland.apollo.olxcdn.com:443/v1/files/efa9any4ryrb-PL/image;s=1000x700\" } ], \"price\": { \"value\": \"9\", \"currency\": \"PLN\", \"negotiable\": false, \"budget\": false, \"trade\": false }, \"salary\": null, \"attributes\": [ { \"code\": \"state\", \"value\": \"used\", \"values\": null } ], \"courier\": null }, { \"id\": 643435839, \"status\": \"removed_by_user\", \"url\": \"https://www.olx.pl/d/oferta/opona-4-80-4-00-8-do-taczki-nowa-CID628-IDHxN3p.html\", \"created_at\": \"2020-11-27 11:53:47\", \"activated_at\": \"2020-11-27 11:54:36\", \"valid_to\": \"2020-12-17 15:38:07\", \"title\": \"opona 4.80/4.00 - 8 do taczki nowa!!!\", \"description\": \"opona do taczki, nowa, nigdy nie używana, stan idealny.\\r\\nrozmiar 4.80/4.00-8. \\r\\nopona do taczki, nowa, nigdy nie używana, stan idealny.\\r\\nrozmiar 4.80/4.00-8.\", \"category_id\": 1636, \"advertiser_type\": \"private\", \"external_id\": null, \"external_url\": null, \"contact\": { \"name\": \"Damazy\", \"phone\": \"501474399\" }, \"location\": { \"city_id\": 10609, \"district_id\": 301, \"latitude\": \"51.80178\", \"longitude\": \"19.43928\" }, \"images\": [ { \"url\": \"https://ireland.apollo.olxcdn.com:443/v1/files/qmvssagjnq1r2-PL/image;s=1000x700\" } ], \"price\": { \"value\": \"9\", \"currency\": \"PLN\", \"negotiable\": false, \"budget\": false, \"trade\": false }, \"salary\": null, \"attributes\": [ { \"code\": \"state\", \"value\": \"new\", \"values\": null } ], \"courier\": null } ]";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Advert[] adverts = objectMapper.readValue(advertsString, Advert[].class);
for (Advert advert : adverts) {
    Advert saved = advertRepository.save(advert);
    System.out.println("saved " + saved.getIdent());
}

I hope your problem gets resolved which you have been stuck with for one week. If you don't want to manually create these types of descriptors you can follow this article to use as an external dependency.

Sign up to request clarification or add additional context in comments.

3 Comments

I have edited the answer. Please check the answer again. Hope this time it will solve your problem.
Hey @DamazyKowalski , did this work for you?
sorry, I had no time to check this solution. I promise I will check it asap.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.