【问题标题】:How to persist Json Object with nested arrays (including Json objects) to sql database in one table?如何将带有嵌套数组(包括 Json 对象)的 Json 对象保存到一张表中的 sql 数据库中?
【发布时间】:2021-10-13 20:41:15
【问题描述】:

我在 Spring 中编写应用程序。

这是我的 json:(它是一个 json 对象数组)

[{"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
}]

这是我的实体类广告:

@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;

还有我的 saveAdverts 方法:

@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";
    }

我想要做的是将json解析为实体对象并将所有广告对象保存到一个表中的sql数据库中。 方法执行在此行停止并出现异常:

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

我收到此错误消息:

com.fasterxml.jackson.databind.exc.MismatchedInputException:无法从数组值(令牌JsonToken.START_ARRAY)中反序列化类型为java.lang.String 的值 在 [来源:(StringReader); line: 24, column: 14](通过引用链:java.lang.Object[][0]->pl.vida.model.Advert["images"])

请注意,“images”字段对应于 json 对象的嵌套数组。 请帮忙,我花了一周的时间没有结果。谢谢

【问题讨论】:

  • 问几个问题: 1. 您是使用Hibernate 还是Spring Data JPA 来存储数据? 2. 你的 API 工作正常吗?我的意思是你是否在String adverts=... 中获取 API 数据? 3. 你用的是哪个数据库?
  • 您好,我正在使用 Hibernate,是的,我在可变广告中有数据。问题在于解析对象

标签: java sql arrays json spring


【解决方案1】:

com.fasterxml.jackson.databind.exc.MismatchedInputException:无法从 [Source: (StringReader) 的数组值(令牌 JsonToken.START_ARRAY)中反序列化 java.lang.String 类型的值; line: 24, column: 14](通过引用链:java.lang.Object[][0]->pl.vida.model.Advert["images"])

您遇到上述异常是因为您试图将对象数组转换为不可能的字符串。在您的 JSON 中看到 imagesattributes 是对象数组。

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

在您的 Advert 类中,您创建了 imagesattributes 作为字符串类型。

private String attributes;
private String images;

通常,对于array 类型的对象,我们创建字段ListSet,如果字段是List/Set,那么我们需要为它们创建单独的类并映射为OneToMany 关系。因此,创建单独的类意味着将创建单独的表,但您不希望拥有多个表。您希望将所有数据存储在一个表中。在正常情况下,这是不可能的,但如果我们编写一些额外的配置类,那么我们可以满足您的要求。这些调整是由 Hibernate 自己提供的。

所以基本上,Hibernate 提供了一些内置类型,如 String、Integer、Float、Date、Timezone 等Here you can check the complete list of built-in types。但是根据我们的要求,我们也可以创建自定义类型。所以要存储array 类型的数据,Hibernate 没有提供任何内置类型。因此,我们将创建一个自定义类型。

解决方案: 所以我们想存储一个对象数据数组,我们可以很容易地将它存储在com.fasterxml.jackson.databind.JsonNode对象中。但是 Hibernate 不支持这个类作为字段类型。因此,为了支持这个类,我们需要编写 2 个额外的类,即 JsonNodeStringType.javaJsonNodeStringDescriptor

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());
    }
}

现在我们的Advert 类将如下所示

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;
}

我们开始吧。如果您执行以下代码,它会完美运行。

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());
}

我希望您困扰了一个星期的问题得到解决。如果您不想手动创建这些类型的描述符,您可以按照此article 将其用作外部依赖项。

【讨论】:

  • 我已经编辑了答案。请再次检查答案。希望这次能解决你的问题。
  • 嘿@DamazyKowalski,这对你有用吗?
  • 对不起,我没有时间检查这个解决方案。我保证我会尽快检查。
  • 是的,它对我有用,非常感谢。问候
猜你喜欢
  • 2020-12-18
  • 1970-01-01
  • 2021-12-19
  • 2016-12-11
  • 2019-08-11
  • 2013-09-29
  • 2021-07-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多