a better way than ```val tuples = receiveText() ...
# codereview
e
a better way than
Copy code
val tuples = receiveText()
    .drop(1).dropLast(1)
    .split(',')
    .map { it.substringBefore(':') to it.substringAfter(':') }
to transform something like this
{"event_type":"pre-commit","event_time":"2024-02-15T095148Z","action_name":"Dataset","hook_id":"dataset_validator","repository_id":"quickstart","branch_id":"main","source_ref":"main","commit_message":"asd","committer":"quickstart"}
into a
List<Pair<String, String>>
?
h
Yes, use a proper JSON parser () and iterate object properties to get the list of pairs. For example, if any of your values contains a
,
, your code would break. Using a real parser fixes that.
6
e
I'd like to use camel case named fields, enums, `Instant`s and custom transformations
s
I don't understand how that's a response to what Henning said—any decent JSON library can be configured to do all of that
in fact, I have done literally all of what you've asked for with Jackson in Java
e
I believe you, but for my use case (some quick prototyping) I think it's overkill, I managed to parse it properly into a Class in 19 lines in few minutes of coding
ok, polishing phase now, I'll give a JSON parser a try
@Shawn how did you conditionally parse specific fields? I need to parse
branchId
only if
eventType
is not a specific enum
@hho do you happen to have any idea about conditional deserialization?
h
What do you want to achieve? For your List of Strings you can just do something like this:
Copy code
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue

val json = ObjectMapper().findAndRegisterModules()

fun parseJson(input: String): List<Pair<String, String>> {
    val parsed = json.readValue<MutableMap<String,String>>(input)

    if (parsed["event_type"] != "pre-commit") { // whatever your condition here
        parsed.remove("branch_id")
    }

    return parsed.toList()
}

fun main() {
    println(parseJson("""{"event_type":"pre-commit","event_time":"2024-02-15T09:51:48Z","action_name":"Dataset","hook_id":"dataset_validator","repository_id":"quickstart","branch_id":"main","source_ref":"main","commit_message":"asd","committer":"quickstart"}"""))
}
(that is with
jackson-databind
and
jackson-module-kotlin
on the classpath). However, what you're describing sounds more like you have different event_types which should be deserialized into different types in your app as well. With Jackson, that could look something like this:
Copy code
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import <http://com.fasterxml.jackson.annotation.JsonTypeInfo.As|com.fasterxml.jackson.annotation.JsonTypeInfo.As>
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue

val json = ObjectMapper().findAndRegisterModules()

fun parseJson(input: String): BaseEvent {
    return json.readValue(input)
}

fun main() {
    println(parseJson("""{"event_type":"pre-commit","event_time":"2024-02-15T09:51:48Z","action_name":"Dataset","hook_id":"dataset_validator","repository_id":"quickstart","branch_id":"main","source_ref":"main","commit_message":"asd","committer":"quickstart"}"""))
    println(parseJson("""{"event_type":"something-else","event_time":"2024-02-15T09:51:48Z","action_name":"Foo","hook_id":"Bar","some_other_field":"Baz"}"""))
}

@JsonTypeInfo(include = As.PROPERTY, property = "event_type", use = JsonTypeInfo.Id.NAME)
@JsonSubTypes(
    JsonSubTypes.Type(value = PreCommitEvent::class, name = "pre-commit"),
    JsonSubTypes.Type(value = SomeOtherEvent::class, name = "something-else")
)
interface BaseEvent {
    val event_time: String
    val action_name: String
    val hook_id: String
}

data class PreCommitEvent(
    override val event_time: String,
    override val action_name: String,
    override val hook_id: String,
    val repository_id: String,
    val branch_id: String,
    val source_ref: String,
    val commit_message: String,
    val committer: String
) : BaseEvent

data class SomeOtherEvent(
    override val event_time: String,
    override val action_name: String,
    override val hook_id: String,
    val some_other_field: String
) : BaseEvent
e
Copy code
{
  "event_type": "pre-merge",
  "event_time": "2021-02-28T14:03:31Z",
  "action_name": "test action",
  "hook_id": "prevent_user_columns",
  "repository_id": "repo1",
  "branch_id": "feature-1",
  "source_ref": "feature-1",
  "commit_message": "merge commit message",
  "committer": "committer",
  "commit_metadata": {
    "key": "value"
  }
}
I don't want to deserialize
branch_id
(because it won't be there and it should be
null
in the corresponding class) if
event_type
is a specific value(s) https://docs.lakefs.io/howto/hooks/webhooks.html#request-body-schema
s
I meant to reply to this a week ago, but Jackson can be configured to deserialize missing properties as
null
, so you don't really need to switch on
event_type
e
I need essentially conditional deserialization the only way to achieve that I found so far, is to write your own Serializer (which actually defy the point of using a JSON parser in the first place)
s
why do you need it to be conditional? do you ever expect that data to be present even when
event_type
is one of those specific values?
e
according to the docs no, but I'd like to enforce that at priori
also, I had encountered other user cases in the past where I needed exactly that, so I'd like to achieve that, if possible