Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
2.0k views
in Technique[技术] by (71.8m points)

vb.net - Better way to deserialize Minecraft json

I am trying to work with Minecraft json from (1.8.json download). A Sample:

{
  "objects": {
    "realms/lang/de_DE.lang": {
      "hash": "729b2c09d5c588787b23127eeda2730f9c039194",
      "size": 7784
    },
    "realms/lang/cy_GB.lang": {
      "hash": "7b52463b2df4685d2d82c5d257fd5ec79843d618",
      "size": 7688
    },
    "minecraft/sounds/mob/blaze/breathe4.ogg": {
      "hash": "78d544a240d627005aaef6033fd646eafc66fe7a",
      "size": 22054
    },
    "minecraft/sounds/dig/sand4.ogg": {
      "hash": "37afa06f97d58767a1cd1382386db878be1532dd",
      "size": 5491
    }
  }
}

The actual json is much longer, some 2940 lines.

I need a way to deserialize this that isn't completely insane - using JSONUtils I get 4411 Lines of Code, but the same code can't be used for any other version of Minecraft.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The automated tools can be very useful, but they are not perfect - especially when it comes to dictionaries.

The first thing to note is that the structure is the same for all of them, that is they all consist of hash and size properties. This means rather than creating hundreds of identical classes we can use the same one over and over:

' the type that repeats from your robot:
Public Class MinecraftItem
    Public Property hash As String
    Public Property size As Int32
End Class

Since the automated tools are working on the fly (they do not look ahead...or back) they don't really know that they are all the same. The next thing is that the classes the robots generate wont work in this case:

Public Property minecraft/sounds/music/game/creative/creative3.ogg As _
            MinecraftSoundsMusicGameCreativeCreative3Ogg

That is illegal as a property name. However, the names will work just fine as Dictionary keys. In addition to the above MinecraftItem class, we might want a container class:

Public Class MinecraftContainer
    Public objects As Dictionary(Of String, MinecraftItem)
End Class

There are (at least) 3 ways to get at the data:

Method 1: Pluck out a single item

Imports Newtonsoft.Json
Imports Newtonsoft.Json.Linq

Dim jstr As String = ...from whereever

' parse the json into a JObject
Dim js As JObject = JObject.Parse(jstr)

' if you somehow know the names, you can pluck out the data:
Dim mi = js("objects")("minecraft/sounds/mob/blaze/hit2.ogg")

Console.WriteLine(mi("hash").ToString & "    " & mi("size").ToString)

The first line parses the original json string into a JObject. This allows us to work with the contents in various ways. For instance, we can reference "objects" in the json and them the items by name, which is exactly what happens in the next line:

' drill into "objects", get the "...hit2.ogg" item
Dim mi = js("objects")("minecraft/sounds/mob/blaze/hit2.ogg")

This will work if you just need to fetch a particular item from the big file. The mi variable created is a "special" json token, so use the names to get the data bits you want

hash = mi("hash").ToString
size = mi("size").ToString

Method 2: Deserialize To a Dictionary

This would be my preferred method. Include the same Import statements as in the first example, then:

' parse the json string
Dim js As JObject = JObject.Parse(jstr)

' deserialize the inner "objects" to a NET Dictionary
Dim myItems = JsonConvert.DeserializeObject(Of Dictionary(Of String, _
                   MinecraftItem))(js("objects").ToString)

This will create myItems as a Net Dictionary(Of String, MincraftItem) from the json. Since the MinecraftObject class doesn't do anything but hold the dictionary, we skipped it.

The keys are the long names and each value is a MinecraftItem which allows you to reference them more conventionally:

' get one of the items into a variable
gravel3 = myItems("minecraft/sounds/mob/chicken/step2.ogg")
ConsoleWriteLine("Gravel3  hash: {0},  size: {1}",
                      gravel3.hash, gravel3.size.ToString)

If you are not familiar with the .Net Dictionary it is somewhat like an array or List except you access your items by a Key rather then index. To loop thru them all:

Dim n As Integer = 0
For Each kvp As KeyValuePair(Of String, MinecraftItem) In myItems
    Console.WriteLine("Name: {0}  Hash: {1}  size: {2}",
                      kvp.Key, 
                      kvp.Value.hash, 
                      kvp.Value.size.ToString)
    n += 1
    If n >= 2 Then Exit For           ' just print 3
Next                              

Output:

Name: realms/lang/de_DE.lang Hash: 10a54fc66c8f479bb65c8d39c3b62265ac82e742 size: 8112
Name: realms/lang/cy_GB.lang Hash: 14cfb2f24e7d91dbc22a2a0e3b880d9829320243 size: 7347
Name: minecraft/sounds/mob/chicken/step2.ogg Hash: bf7fadaf64945f6b31c803d086ac6a652aabef9b size: 3838

Just remember, the Key will always be the long path name, and each .Value is MinecraftItem object.

Method 3: Deserialize To a Container

You can skip the parsing step by using the MinecraftContainer class:

Dim jstr As String = ...from whereever
Dim myJItems = JsonConvert.DeserializeObject(Of MinecraftContainer)(jstr)

Note that this time, you pass the entire string you downloaded to JsonConvert. The result will be an extra outer object which contains a dictionary property named "Objects". So, you reference the items using some leading references:

gravel3hash = myJItems.Object("minecraft/sounds/dig/gravel3.ogg").hash

gravel3 = myJItems.Object("minecraft/sounds/dig/gravel3.ogg")
    ConsoleWriteLine("Gravel3  hash: {0},  size: {1}",
                      gravel3.hash, gravel3.size.ToString)
'or:
ConsoleWriteLine("Gravel3  hash: {0},  size: {1}",
                   myJItems.Object("minecraft/sounds/dig/gravel3.ogg").hash, 
                   myJItems.Object("minecraft/sounds/dig/gravel3.ogg").size.ToString)   

This method is just one line of code to deserialize, but it means
a) I have to define the container class and
b) I have to use myJItems.Object to drill into the otherwise empty container to get at the Dictionary.

Personally, I would forego this and use method 2 - one extra line of code makes it a bit easier to work with. That said, you can also extract the dictionary collection from the container:

Dim myItems As Dictionary(Of String, MinecraftItem)= myJItems.Object

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...