You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在不使用公共setter的情况下将JSON反序列化到私有字段?

无公共Setter且符合设计原则的JSON反序列化方案

针对你遇到的「不想用公共Setter,又要避免违反开闭原则(OCP)、里氏替换原则(LSP)」的问题,以下是几种可行方案:

一、Jackson 直接操作私有字段(最简方案)

Jackson本身支持通过反射访问私有字段,完全不需要公共Setter,两种实现方式:

1. 给私有字段加@JsonProperty注解

修改Food类,直接在私有字段上标记注解,指定JSON映射关系,保留必要的Getter即可:

public class Food {
    @JsonProperty
    private String name;
    @JsonProperty
    private double calories;
    @JsonProperty("serving_size_g") // 直接对应JSON字段名,替代@JsonAlias
    private double servingSize;

    // 仅保留业务需要的Getter,无任何Setter
    public String getName() {
        return name;
    }

    public double getCalories() {
        return calories;
    }

    public double getServingSize() {
        return servingSize;
    }
}

原有的getFood方法不需要修改,ObjectMapper会自动通过反射将JSON值注入私有字段。

2. 全局配置ObjectMapper允许访问私有字段

如果不想给每个字段加注解,可全局开启私有字段访问权限:

ObjectMapper objectMapper = new ObjectMapper();
// 允许Jackson直接访问所有字段(包括私有)
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

此时字段名默认和JSON键名匹配,需要别名的字段仍可单独加@JsonProperty("serving_size_g")。

二、Gson 反射映射(告别自定义TypeAdapter)

Gson默认支持反射访问私有字段,无需写那种带switch的TypeAdapter,只需用@SerializedName指定字段映射:

修改Food类:

public class Food {
    @SerializedName("name")
    private String name;
    @SerializedName("calories")
    private double calories;
    @SerializedName("serving_size_g")
    private double servingSize;

    // 仅保留Getter
    public String getName() {
        return name;
    }

    public double getCalories() {
        return calories;
    }

    public double getServingSize() {
        return servingSize;
    }
}

反序列化代码直接用Gson即可:

private Food getFood(String foodName) throws IOException {
    String nutritionJsonString = apiConnector.getNutritionAsStringFromAPI(foodName);
    return new Gson().fromJson(nutritionJsonString, Food.class);
}

Gson会自动通过反射给私有字段赋值,完全不需要Setter,也不会出现违反OCP的switch逻辑。

三、Builder模式 + 序列化器(灵活扩展,完全符合OCP)

如果需要更强的扩展性,同时避免反射,Builder模式是最优选择——新增字段只需修改Builder,无需改动反序列化核心逻辑:

1. 实现Food的Builder模式

public class Food {
    // 私有不可变字段,保证线程安全和封装性
    private final String name;
    private final double calories;
    private final double servingSize;

    // 私有构造方法,只能通过Builder创建对象
    private Food(Builder builder) {
        this.name = builder.name;
        this.calories = builder.calories;
        this.servingSize = builder.servingSize;
    }

    // 仅保留Getter
    public String getName() { return name; }
    public double getCalories() { return calories; }
    public double getServingSize() { return servingSize; }

    // Builder内部类
    public static class Builder {
        private String name;
        private double calories;
        private double servingSize;

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder calories(double calories) {
            this.calories = calories;
            return this;
        }

        public Builder servingSize(double servingSize) {
            this.servingSize = servingSize;
            return this;
        }

        public Food build() {
            return new Food(this);
        }
    }
}

2. Jackson适配Builder

Jackson支持直接反序列化到Builder,只需两步:

  • 在Food类上添加@JsonDeserialize(builder = Food.Builder.class)注解
  • 在Builder的每个方法上添加@JsonProperty对应JSON字段:
public static class Builder {
    private String name;
    private double calories;
    private double servingSize;

    @JsonProperty("name")
    public Builder name(String name) {
        this.name = name;
        return this;
    }

    @JsonProperty("calories")
    public Builder calories(double calories) {
        this.calories = calories;
        return this;
    }

    @JsonProperty("serving_size_g")
    public Builder servingSize(double servingSize) {
        this.servingSize = servingSize;
        return this;
    }

    public Food build() {
        return new Food(this);
    }
}

反序列化代码无需修改,Jackson会自动调用Builder方法构建对象,新增字段时只需在Builder中添加对应方法和注解,完全符合OCP。

3. Gson适配Builder

Gson可通过自定义通用BuilderTypeAdapter实现,避免重复代码,同时完整实现序列化/反序列化(符合LSP):

// 通用Builder类型适配器
public class BuilderTypeAdapter<T, B> extends TypeAdapter<T> {
    private final Supplier<B> builderSupplier;
    private final Function<B, T> buildFunction;
    private final Map<String, BiConsumer<B, Object>> fieldSetters;

    public BuilderTypeAdapter(Supplier<B> builderSupplier, Function<B, T> buildFunction, Map<String, BiConsumer<B, Object>> fieldSetters) {
        this.builderSupplier = builderSupplier;
        this.buildFunction = buildFunction;
        this.fieldSetters = fieldSetters;
    }

    @Override
    public void write(JsonWriter out, T value) throws IOException {
        // 完整实现序列化逻辑,符合里氏替换原则
        Food food = (Food) value;
        out.beginObject();
        out.name("name").value(food.getName());
        out.name("calories").value(food.getCalories());
        out.name("serving_size_g").value(food.getServingSize());
        out.endObject();
    }

    @Override
    public T read(JsonReader in) throws IOException {
        B builder = builderSupplier.get();
        in.beginObject();
        while (in.hasNext()) {
            String fieldName = in.nextName();
            BiConsumer<B, Object> setter = fieldSetters.get(fieldName);
            if (setter != null) {
                switch (in.peek()) {
                    case STRING:
                        setter.accept(builder, in.nextString());
                        break;
                    case NUMBER:
                        setter.accept(builder, in.nextDouble());
                        break;
                    default:
                        in.skipValue();
                }
            } else {
                in.skipValue();
            }
        }
        in.endObject();
        return buildFunction.apply(builder);
    }
}

然后给Food类添加@JsonAdapter注解:

@JsonAdapter(Food.FoodTypeAdapter.class)
public class Food {
    // 原有Food和Builder代码不变

    public static class FoodTypeAdapter extends BuilderTypeAdapter<Food, Builder> {
        public FoodTypeAdapter() {
            super(Builder::new, Builder::build, Map.of(
                    "name", (builder, val) -> builder.name((String) val),
                    "calories", (builder, val) -> builder.calories((Double) val),
                    "serving_size_g", (builder, val) -> builder.servingSize((Double) val)
            ));
        }
    }
}

新增字段时只需在Map.of中添加对应映射,无需修改read方法的核心逻辑,完美符合OCP;同时write方法完整实现序列化,不会违反LSP。


内容的提问来源于stack exchange,提问作者SaftwareEnginnering

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 08:01:46