I'm rewriting some java code which is using jackson for json parsing into scala circe.
The java Device
class is this -
package forjava;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@JsonInclude(Include.NON_NULL)
@JsonPropertyOrder({"ua", "dnt", "ip", "devicetype"})
public class Device implements Serializable {
@JsonProperty("ua")
private String ua;
@JsonProperty("dnt")
private Integer dnt;
@JsonProperty("ip")
private String ip;
@JsonProperty("devicetype")
private Integer devicetype;
@JsonIgnore
private Map<String, Object> additionalProperties = new HashMap();
@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
@JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
private static final long serialVersionUID = -4938649324295079141L;
public Device() {
}
public Device(String ua, Integer dnt, Integer lmt, String ip, String ipv6, Integer devicetype, String make) {
this.ua = ua;
this.dnt = dnt;
this.ip = ip;
this.devicetype = devicetype;
}
@JsonProperty("ua")
public String getUa() {
return this.ua;
}
@JsonProperty("ua")
public void setUa(String ua) {
this.ua = ua;
}
@JsonProperty("dnt")
public Integer getDnt() {
return this.dnt;
}
@JsonProperty("dnt")
public void setDnt(Integer dnt) {
this.dnt = dnt;
}
@JsonProperty("ip")
public String getIp() {
return this.ip;
}
@JsonProperty("ip")
public void setIp(String ip) {
this.ip = ip;
}
@JsonProperty("devicetype")
public Integer getDevicetype() {
return this.devicetype;
}
@JsonProperty("devicetype")
public void setDevicetype(Integer devicetype) {
this.devicetype = devicetype;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(Device.class.getName()).append('@').append(Integer.toHexString(System.identityHashCode(this))).append('[');
sb.append("ua");
sb.append('=');
sb.append(this.ua == null ? "<null>" : this.ua);
sb.append(',');
sb.append("dnt");
sb.append('=');
sb.append(this.dnt == null ? "<null>" : this.dnt);
sb.append(',');
sb.append("ip");
sb.append('=');
sb.append(this.ip == null ? "<null>" : this.ip);
sb.append(',');
sb.append("devicetype");
sb.append('=');
sb.append(this.devicetype == null ? "<null>" : this.devicetype);
sb.append(',');
sb.append("additionalProperties");
sb.append('=');
sb.append(this.additionalProperties == null ? "<null>" : this.additionalProperties);
sb.append(',');
if (sb.charAt(sb.length() - 1) == ',') {
sb.setCharAt(sb.length() - 1, ']');
} else {
sb.append(']');
}
return sb.toString();
}
public int hashCode() {
int result = 1;
result = result * 31 (this.ua == null ? 0 : this.ua.hashCode());
result = result * 31 (this.devicetype == null ? 0 : this.devicetype.hashCode());
result = result * 31 (this.ip == null ? 0 : this.ip.hashCode());
result = result * 31 (this.dnt == null ? 0 : this.dnt.hashCode());
return result;
}
public boolean equals(Object other) {
if (other == this) {
return true;
} else if (!(other instanceof Device)) {
return false;
} else {
Device rhs = (Device)other;
return (Objects.equals(this.ua, rhs.ua))
&& (Objects.equals(this.devicetype, rhs.devicetype) || this.devicetype != null
&& this.devicetype.equals(rhs.devicetype))
&& (Objects.equals(this.ip, rhs.ip))
&& (Objects.equals(this.dnt, rhs.dnt)) ;
}
}
}
Below is the java code to parsing the json -
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class DeviceJsonParserDemo {
public static void main(String[] args) throws IOException {
ObjectMapper jsonMapperBidRequest = new ObjectMapper();
String dev = Files.readString(Paths.get("src/main/resources/device.json"));
Device device = jsonMapperBidRequest.readValue(dev, Device.class);
System.out.println(device);
}
}
When you run the above code you will get the below output -
Device@5876a9af[ua=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36,dnt=<null>,ip=<null>,devicetype=<null>,additionalProperties={started=now, devtime=now}]
As you notice in the above output, the fields that are not in Device class are in the additionalProperties field.
Now I want to migrate the above logic into typelevel circe
I have below Decoded
import io.circe.{Decoder, HCursor}
case class Device (ua: String, dnt: Option[Int], ip: Option[String], devicetype: Option[Int], addlProperties: Map[String, Any] = Map.empty)
object Device {
implicit val decodeFoo: Decoder[Device] = new Decoder[Device] {
final def apply(c: HCursor): Decoder.Result[Device] =
for {
ua <- c.downField("ua").as[String]
dnt <- c.downField("dnt").as[Option[Int]]
ip <- c.downField("ip").as[Option[String]]
devicetype <- c.downField("devicetype").as[Option[Int]]
} yield new Device(ua, dnt, ip, devicetype)
}
}
The java parsing logic is
import scala.io.Source
import io.circe.parser._
object DeviceJsonCirceDecodeDemo extends App {
val devString = Source.fromFile("src/main/resources/device.json").mkString
val device = decode[Device](devString)
println(device)
}
When I run the above code I get the below output -
Right(Device(Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36,None,None,None,Map()))
As you see here, the addlProperties
map is empty because it doesn't have decoder.
The code is in github here for java and for scala here How can I achieve the same in circe.
CodePudding user response:
A relatively simple if somewhat awkward to maintain solution is to decode the full object as a Map and removing the known keys.
Your decoder would become:
implicit val decodeFoo: Decoder[Device] = Decoder.instance(c =>
for {
ua <- c.get[String]("ua")
dnt <- c.get[Option[Int]]("dnt")
ip <- c.get[Option[String]]("ip")
devicetype <- c.get[Option[Int]]("devicetype")
additional <- c.as[Map[String, Json]].map(_ - "ua" - "dnt" - "ip" - "devicetype")
} yield new Device(ua, dnt, ip, devicetype, additional)
)
I've replaced new Decoder
with Decoder.instance
and c.downField(...).as[...]
with c.get[...](...)
.
This also assumes addlProperties
is a Map[String, Json]
, you might want to define a decoder for a specific set of types.