1. 概述

JSON-LD 是一种基于 JSON 的 RDF 格式,用于表达 Linked Data。它的核心优势在于:可以在标准 JSON 对象中嵌入超媒体能力,也就是说,能让数据自带机器可读的链接信息,实现资源之间的语义关联。

本文将重点探讨如何使用 Jackson 生态下的工具,直接将 POJO 序列化/反序列化为 JSON-LD 格式。我们也会快速回顾 JSON-LD 的核心概念,以便理解后续示例。

⚠️ 注意:本文不教你 JSON 基础,假定你已熟悉 Jackson 的 ObjectMapper、注解机制和自定义序列化器。


2. JSON-LD 核心概念

第一次看 JSON-LD 文档时,你会注意到某些字段名以 @ 开头,这些是 JSON-LD 的关键字(keywords),它们定义了文档的语义结构。

要理解本文内容,你只需掌握以下四个关键字:

  • @context:上下文定义,一个键值映射,告诉解析器如何解释文档中的属性
  • @vocab@context 中的一个可选键,用于设置默认词汇表,简化属性命名
  • @id:资源的唯一标识符(URI),也可用于表示某个字段是链接
  • @type:资源的类型标识,可出现在资源级别或 @context

✅ 简单理解:@context 是“字典”,@id 是“地址”,@type 是“身份”,@vocab 是“缩写规则”。


3. Java 中的序列化挑战

熟悉 Jackson 的你可能会想:用 @JsonProperty("@id") 不就能搞定 @id 字段?确实可以。

但问题在于 @context —— 手写 @context 不仅繁琐,还极易出错,尤其是当字段多、命名空间复杂时。

因此,我们更倾向于使用专门的库来自动化生成 @context。本文介绍两个基于 Jackson 的方案:jackson-jsonldhydra-jsonld。⚠️ 但要提前说明:它们都不支持 JSON-LD 全部特性,选择前务必评估其局限。


4. 使用 Jackson-Jsonld 序列化

jackson-jsonld 是一个 Jackson 模块,通过注解方式让 POJO 轻松生成 JSON-LD。

4.1 Maven 依赖

<dependency>
    <groupId>com.io-informatics.oss</groupId>
    <artifactId>jackson-jsonld</artifactId>
    <version>0.1.1</version>
</dependency>

4.2 示例代码

@JsonldResource
@JsonldNamespace(name = "s", uri = "http://schema.org/")
@JsonldType("s:Person")
@JsonldLink(rel = "s:knows", name = "knows", href = "http://example.com/person/2345")
public class Person {
    @JsonldId
    private String id;
    @JsonldProperty("s:name")
    private String name;

    // constructor, getters, setters
}

关键注解解析:

  • @JsonldResource:标记该类为 JSON-LD 资源
  • @JsonldNamespace:定义命名空间别名,s 代表 http://schema.org/
  • @JsonldType:指定资源类型为 s:Person
  • @JsonldLink:添加静态链接字段 knowsrel 决定其语义,href 是目标地址
  • @JsonldId:该字段映射为 @id
  • @JsonldProperty:指定字段在 @context 中的映射关系

序列化代码:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonldModule());

Person person = new Person("http://example.com/person/1234", "Example Name");
String personJsonLd = objectMapper.writeValueAsString(person);

✅ 输出结果:

{
  "@type": "s:Person",
  "@context": {
    "s": "http://schema.org/",
    "name": "s:name",
    "knows": {
      "@id": "s:knows",
      "@type": "@id"
    }
  },
  "name": "Example Name",
  "@id": "http://example.com/person/1234",
  "knows": "http://example.com/person/2345"
}

4.3 注意事项

使用前请评估以下限制:

  • ❌ 不支持 @vocab,必须显式定义命名空间或写全 IRI
  • ❌ 链接(@JsonldLink)只能在编译期定义,无法动态添加
  • ⚠️ 项目维护不活跃,生产环境需谨慎

5. 使用 Hydra-Jsonld 序列化

Hydra-JsonldHydra-Java 的模块,本为 Spring 应用设计,但其核心序列化器也可独立使用。

它基于 Hydra Vocabulary,生成更具语义的 JSON-LD。

5.1 Maven 依赖

<dependency>
    <groupId>de.escalon.hypermedia</groupId>
    <artifactId>hydra-jsonld</artifactId>
    <version>0.4.2</version>
</dependency>

5.2 示例代码

@Vocab("http://example.com/vocab/")
@Expose("person")
public class Person {
    private String id;
    private String name;

    public Person(String id, String name) {
        this.id = id;
        this.name = name;
    }

    @JsonProperty("@id")
    public String getId() {
        return id;
    }

    @Expose("fullName")
    public String getName() {
        return name;
    }
}

关键点说明:

  • @Vocab:设置默认词汇表
  • @Expose:类上使用定义 @type,方法上使用定义属性映射
  • @JsonProperty("@id"):从 getter 返回 @id

注册自定义模块:

SimpleModule getJacksonHydraSerializerModule() {
    return new SimpleModule() {
        @Override
        public void setupModule(SetupContext context) {
            super.setupModule(context);

            context.addBeanSerializerModifier(new BeanSerializerModifier() {
                @Override
                public JsonSerializer<?> modifySerializer(
                  SerializationConfig config, 
                  BeanDescription beanDesc, 
                  JsonSerializer<?> serializer) {
                    if (serializer instanceof BeanSerializerBase) {
                        return new JacksonHydraSerializer((BeanSerializerBase) serializer);
                    } else {
                        return serializer;
                    }
                }
            });
        }
    };
}

序列化调用:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(getJacksonHydraSerializerModule());
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

Person person = new Person("http://example.com/person/1234", "Example Name");
String personJsonLd = objectMapper.writeValueAsString(person);

✅ 输出结果:

{
  "@context": {
    "@vocab": "http://example.com/vocab/",
    "name": "fullName"
  },
  "@type": "person",
  "name": "Example Name",
  "@id": "http://example.com/person/1234"
}

5.3 注意事项

  • ✅ 与 Spring 集成时功能更完整(如自动 HATEOAS 链接)
  • 独立使用时无法通过注解添加自定义链接
  • ❌ 无法禁用 @vocab,只能覆盖
  • ⚠️ 设计初衷是配合 Spring-HATEOAS,非通用 JSON-LD 解决方案

6. 使用 Jsonld-Java 反序列化

Jsonld-Java 是 JSON-LD 1.0 的 Java 实现(⚠️ 非最新 1.1)。若需 1.1 支持,可考虑 Titanium JSON-LD

反序列化的核心思路是:先用 JSON-LD API 将原始文档“压缩”(compact),再用 Jackson 映射到 POJO

6.1 Maven 依赖

<dependency>
    <groupId>com.github.jsonld-java</groupId>
    <artifactId>jsonld-java</artifactId>
    <version>0.13.0</version>
</dependency>

6.2 示例代码

输入 JSON-LD:

{
  "@context": {
    "@vocab": "http://schema.org/",
    "knows": {
      "@type": "@id"
    }
  },
  "@type": "Person",
  "@id": "http://example.com/person/1234",
  "name": "Example Name",
  "knows": "http://example.com/person/2345"
}

压缩处理:

Object jsonObject = JsonUtils.fromString(inputJsonLd);
Object compact = JsonLdProcessor.compact(jsonObject, new HashMap<>(), new JsonLdOptions());
String compactContent = JsonUtils.toString(compact);

✅ 压缩后输出:

{
  "@id": "http://example.com/person/1234",
  "@type": "http://schema.org/Person",
  "http://schema.org/knows": {
    "@id": "http://example.com/person/2345"
  },
  "http://schema.org/name": "Example Name"
}

适配的 POJO:

@JsonIgnoreProperties(ignoreUnknown = true)
public class Person {
    @JsonProperty("@id")
    private String id;
    @JsonProperty("http://schema.org/name")
    private String name;
    @JsonProperty("http://schema.org/knows")
    private Link knows;

    // constructors, getters, setters

    public static class Link {
        @JsonProperty("@id")
        private String id;

        // constructors, getters, setters
    }
}

最终反序列化:

ObjectMapper objectMapper = new ObjectMapper();
Person person = objectMapper.readValue(compactContent, Person.class);

7. 总结

本文介绍了两种 Jackson 集成方案(jackson-jsonldhydra-jsonld)用于 JSON-LD 序列化,以及一种结合 jsonld-java 的反序列化方法。

✅ 优点:简单粗暴,适合固定结构的 JSON-LD 输出
❌ 缺点:功能有限,灵活性差,维护状态参差

📌 踩坑建议

  • 若只是输出简单 JSON-LD,jackson-jsonld 更直接
  • 若已在用 Spring-HATEOAS,hydra-jsonld 集成更顺滑
  • 若需完整 JSON-LD 1.1 支持,建议直接上 RDF 库(如 RDF4J + JSON-LD 输出)

所有示例代码已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/json-modules/json-2


原始标题:Hypermedia Serialization With JSON-LD