From 85ebc439948705ecc9ee0913c60375c13c80284e Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Fri, 24 Jul 2026 03:13:46 +0200 Subject: [PATCH] java: Convert message to and from maps --- codegen/templates/java.java.erb | 57 ++++++++++++++++++++ codegen/templates/json.java.erb | 96 +++++++++++++++++++++++++++++++++ java/Makefile | 1 + 3 files changed, 154 insertions(+) create mode 100644 codegen/templates/json.java.erb diff --git a/codegen/templates/java.java.erb b/codegen/templates/java.java.erb index 2e8a3ce18..6271949a6 100644 --- a/codegen/templates/java.java.erb +++ b/codegen/templates/java.java.erb @@ -6,6 +6,8 @@ import io.cucumber.messages.Property; import org.jspecify.annotations.Nullable; import java.util.List; +import java.util.Map; +import java.util.LinkedHashMap; import java.util.Objects; import java.util.Optional; @@ -96,6 +98,61 @@ public final class <%= class_name(key) %> { <%- end -%> <%- end -%> + Map toJson() { + var json = new LinkedHashMap(); + + <%- schema['properties'].each do |(property_name, property)| + required = (schema['required'] || []).index(property_name) + -%> + <%- if required -%> + <%- if property['items'] -%> + json.put("<%= property_name %>", <%= property_name %>.stream().map(Json::convert).toList()); + <%- else -%> + json.put("<%= property_name %>", Json.convert(<%= property_name %>)); + <%- end -%> + <%- else -%> + if(<%= property_name %> != null){ + <%- if property['items'] -%> + json.put("<%= property_name %>", <%= property_name %>.stream().map(Json::convert).toList()); + <%- else -%> + json.put("<%= property_name %>", Json.convert(<%= property_name %>)); + <%- end -%> + } + <%- end -%> + <%- end -%> + + return json; + } + + //TODO: Rework from fromJson to look like this +// static Rule fromJson(Object json) { +//var map = Json.requireMap(json, "Rule must be a map"); +// return new Rule ( +// Json.readLocation(map.get("location"), "Rule.location"), +// Json.readArray(map.get("tags"), "Rule.tags", Json::readTag) +// ); + } + static <%= class_name(key) %> fromJson(Object json) { + var map = Json.requireMap(json, "<%= class_name(key) %> must be a map"); + return new <%= class_name(key) %> ( +<%- schema['properties'].each_with_index do |(property_name, property), index| -%> + <%- if property['enum'] -%> + <%= type_for(class_name(key), property_name, property) -%>.fromValue(Json.requireString(map.get("<%= property_name %>"), "<%= class_name(key) %>.<%= property_name %> must be a string"))<%= index < schema['properties'].length-1 ? ',' : '' %> + <%- elsif property['type'] == 'string'-%> + Json.requireString(map.get("<%= property_name %>"), "<%= class_name(key) %>.<%= property_name %> must be a string")<%= index < schema['properties'].length-1 ? ',' : '' %> + <%- elsif property['type'] == 'integer'-%> + Json.to<%= type_for(class_name(key), property_name, property) -%>(map.get("<%= property_name %>"), "<%= class_name(key) %>.<%= property_name %> must be an integer ")<%= index < schema['properties'].length-1 ? ',' : '' %> + <%- elsif property['type'] == 'boolean'-%> + Json.requireBoolean(map.get("<%= property_name %>"), "<%= class_name(key) %>.<%= property_name %> must be a boolean")<%= index < schema['properties'].length-1 ? ',' : '' %> + <%- elsif property['type'] == 'array'-%> + Json.requireArray(map.get("<%= property_name %>"), "<%= class_name(key) %>.<%= property_name %> must be an array").stream().map(<%= type_for(class_name(key), nil, property['items']) %>::fromJson).toList()<%= index < schema['properties'].length-1 ? ',' : '' %> + <%- else -%> + <%= type_for(class_name(key), property_name, property) -%>.fromJson(Json.toObject("<%= class_name(key) %>.<%= property_name %> must be a map", map.get("<%= property_name %>")))<%= index < schema['properties'].length-1 ? ',' : '' %> + <%- end -%> +<%- end -%> + ); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/codegen/templates/json.java.erb b/codegen/templates/json.java.erb new file mode 100644 index 000000000..f342bd659 --- /dev/null +++ b/codegen/templates/json.java.erb @@ -0,0 +1,96 @@ +Json.java + package io.cucumber.messages.types; + +import io.cucumber.messages.Property; + +import java.util.List; +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.Objects; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +// Generated code +@SuppressWarnings({"unused", "JavaLangClash"}) +final class Json { + + static Map toObject(String msg, Object o) { + if (o instanceof Map i){ + return (Map)i; + } + throw new IllegalArgumentException(msg); + } + + static Map requireMap(Object o, String msg) { + if (o instanceof Map i){ + return (Map)i; + } + throw new IllegalArgumentException(msg); + } + + + static String requireString(Object o, String msg) { + if (o instanceof String i){ + return i; + } + throw new IllegalArgumentException(msg); + } + + static Boolean requireBoolean(Object o, String msg) { + if (o instanceof Boolean i){ + return i; + } + throw new IllegalArgumentException(msg); + } + + static Long toLong(Object o, String msg) { + if (o instanceof Number i){ + return i.longValue(); + } + throw new IllegalArgumentException(msg); + } + + static Integer toInteger(Object o, String msg) { + if (o instanceof Number i){ + return i.intValue(); + } + throw new IllegalArgumentException(msg); + } + + static List requireArray(Object o, String msg) { + if (o instanceof List i){ + return (List)i; + } + throw new IllegalArgumentException(msg); + } + + static Object convert(String o) { + return o; + } + + static Object convert(Long o) { + return o; + } + + static Object convert(Integer o) { + return o; + } + + + static Object convert(Boolean o) { + return o; + } + + <% @enums.each do |enum| -%> + static Object convert(<%= enum[:name] %> o) { + return o.value(); + } + <% end -%> + <%- @schemas.each do |key, schema| -%> + static Object convert(<%= class_name(key) %> o) { + return o.toJson(); + } + <% end -%> + +} diff --git a/java/Makefile b/java/Makefile index ab0280cd2..814b854ee 100644 --- a/java/Makefile +++ b/java/Makefile @@ -17,6 +17,7 @@ clean: ## Remove automatically generated files and related artifacts .generate-messages: $(schemas) ../codegen/codegen.rb ../codegen/templates/java.java.erb ../codegen/templates/java.enum.java.erb ruby ../codegen/codegen.rb Generator::Java java.java.erb > Generated.java.tmp + ruby ../codegen/codegen.rb Generator::Java json.java.erb >> Generated.java.tmp ruby ../codegen/codegen.rb Generator::Java java.enum.java.erb >> Generated.java.tmp csplit --quiet --prefix=Generated --suffix-format=%02d.java.tmp --elide-empty-files Generated.java.tmp /^[A-Za-z.]*[.]java/ {*} rm Generated.java.tmp