GithubHelp home page GithubHelp logo

Comments (11)

mscheong01 avatar mscheong01 commented on June 12, 2024

Since krotoDC generated class objects can be converted to and from protobuf-java class objects, you can deserialize the json to protobuf-java class object and simply call .toDataClass() (which is extension function generated by krotoDC).
If this does not work for your usecase, please let me know 😄

from krotodc.

anjanbk avatar anjanbk commented on June 12, 2024

I see, it seems like that only works if the serialization also happens from the protobuf-java class (unless I'm missing something else). E.g. as follows:

    val example = TestWrapper(
        data = TestWrapper.Data.TypeA("foobar")
    )

    val javaJson = gson.toJson(example.toProto())
    val ktJson = gson.toJson(example)

    // This does not work
    println(
        gson.fromJson(
            ktJson,
            TestWrapperProtobufJava::class.java).toDataClass())

    // This works
    println(
        gson.fromJson(
            javaJson,
            TestWrapperProtobufJava::class.java).toDataClass()
    )

The problem with that is that serializing the protobuf-java class (i.e. javaJson) ends up looking as follows:

{
    "dataCase_": 1,
    "data_": "foobar",
    "memoizedIsInitialized": 1,
    "unknownFields": {
        "fields": {}
    },
    "memoizedSize": -1,
    "memoizedHashCode": 0
}

grpc-kotlin would also generate classes that serialized this way, and it was one of the main reasons we moved to KrotoDC. We are storing this data in a database, and the expectation is that the data stored looks like the JSON representation of the message.

The use-case I'm trying to accomplish is defining a protobuf message that looks like TestWrapper above), and being able to serialize an instance of it to a Json String that looks like what the Kotlin data class serializes to:

{
    "data": {
        "typeA": "foobar"
    }
}

...and then being able to deserialize that back into the Kotlin data class (either directly or indirectly).

Is there a supported way to do that?

from krotodc.

mscheong01 avatar mscheong01 commented on June 12, 2024

The serialized Protobuf JSON you provided seems to be a direct JSON serialization of the Protobuf Java class, leading to the exposure of hidden fields such as memoizedHashCode, etc., which I believe is the root of your problem.

Have you considered using the serialization methods that Protobuf-Java provides? This approach serializes Protobufs into their corresponding official JSON format(only including proto defined fields), as detailed in this guide: https://www.baeldung.com/java-convert-json-protobuf.

The primary reason I haven't provided a JSON deserialization method for KrotoDC data classes is that doing so would result in a slightly different JSON format compared to the original Protobufs, which seems like a poor design choice. However, adding a utility function to serialize/deserialize from the original Protobuf JSON formats could be a beneficial enhancement to this library.

from krotodc.

mscheong01 avatar mscheong01 commented on June 12, 2024

I've added toJson, fromJson generated functions that provide Json conversion and released it as 1.1.0 🙂

from krotodc.

anjanbk avatar anjanbk commented on June 12, 2024

Just tried it out — there seems to be a bug in the code generator if a message has a long name 😅. Our system has a fairly deep namespace tree, and one of our messages has a somewhat large name, and it triggered the bug.

Try the following:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.example.application.system.types";
option java_outer_classname = "VeryLongMessageNameProto";

package com.example.application.system.types;

message VeryVeryLongTestProtocolBuffersMessageName {
  repeated string type = 1;
}

The fromJson function applies a newline after apply, which doesn't compile

public fun KrotodcVeryVeryLongTestProtocolBuffersMessageName.Companion.fromJson(json: String):
    KrotodcVeryVeryLongTestProtocolBuffersMessageName =
    com.example.application.system.types.VeryVeryLongTestProtocolBuffersMessageName.newBuilder().apply
    { JsonFormat.parser().ignoringUnknownFields().merge(json, this@apply) }.build().toDataClass();

Instead, generating the following should work:

public fun KrotodcVeryVeryLongTestProtocolBuffersMessageName.Companion.fromJson(json: String):
    KrotodcVeryVeryLongTestProtocolBuffersMessageName =
    com.example.application.system.types.VeryVeryLongTestProtocolBuffersMessageName.newBuilder()
        .apply{ JsonFormat.parser().ignoringUnknownFields().merge(json, this@apply) }.build().toDataClass();

from krotodc.

mscheong01 avatar mscheong01 commented on June 12, 2024

Yes, I see... Kotlin poet inserts a newline if the provided code is too long at any whitespace and for cases where the newline is inserted between apply and { the code does not compile 😢 Inserting a newline before .apply will probably fix this issue for good (I would rather not remove the whitespace between apply and { since it is convention to have it)

from krotodc.

mscheong01 avatar mscheong01 commented on June 12, 2024

@anjanbk I've fixed this in #27 and released it as 1.1.1

from krotodc.

anjanbk avatar anjanbk commented on June 12, 2024

That fixed the bug, thanks.

Since Kotlin doesn't let you directly enforce the presence of a companion object's method through a generic type constraint, how would you recommend using the fromJson and toJson that you added in a generic serializer/deserializer?

from krotodc.

mscheong01 avatar mscheong01 commented on June 12, 2024

That doesn't seem possible ATM 😞
If we want to get this supported, we would have to generate conversion functions as class methods instead of extension functions.
Even then, since fromJson is a static function, we'll have to find another way for deserialization.

What would you rate the priority of this feature for your work?
do you have usecases where you use KrotoDC classes as other class' field types?
otherwise, you could just directly call the extension functions.

from krotodc.

anjanbk avatar anjanbk commented on June 12, 2024

An alternative could be to not use sealed classes for oneofs, but that's a bigger change.

The general use-case we have is to define all of our data types in Protobufs, and store those types in our databases as JSONB blobs. Our Kotlin system serializes/deserializes this data when writing/reading that data in the DB. As a result, there needs to be a generic serializer that's able to handle any type as long as that type is defined correctly (e.g. gson.fromJson(jsonString, MyType::class.java). Gson (and most JSON serializers) require custom type adapters to handle sealed classes.

For now, we've defined a type adapter that uses reflection to handle this (something kind of like this: https://gist.github.com/krishnabhargav/7b1832eeb86aa213ba5bb239153977ea)

Would love for there to be a better way to do it though, as this is a common pattern.

from krotodc.

mscheong01 avatar mscheong01 commented on June 12, 2024

I don't have an immediate solution in mind right now 🤔, but it's great to hear you've found a workaround! I'll definitely keep this issue in mind and look into a more permanent solution in the future. For now, I'm going to close this issue and open a new one focused on this specific feature. Also, a big thanks for using krotoDC and sharing your feedback! 🙇

from krotodc.

Related Issues (14)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.