{"id":2365,"date":"2018-01-20T18:30:52","date_gmt":"2018-01-20T10:30:52","guid":{"rendered":"http:\/\/learn-house.idv.tw\/?p=2365"},"modified":"2018-01-31T20:24:58","modified_gmt":"2018-01-31T12:24:58","slug":"parsing-a-cc-struct-in-java","status":"publish","type":"post","link":"https:\/\/learn-house.idv.tw\/?p=2365","title":{"rendered":"[\u8f49]Parsing a C\/C++ struct in Java"},"content":{"rendered":"<p>\u9019\u662f\u4e00\u7bc7\u5099\u4efd\u7684\u6587\u7ae0\uff0c\u9810\u9632\u4e4b\u5f8c\u6d88\u5931\uff0c\u4f86\u6e90\u662f<br \/>\nhttp:\/\/jnkjava.blogspot.tw\/2013\/06\/parsing-cc-struct-in-java.html<\/p>\n<div dir=\"ltr\">\n<div dir=\"ltr\">\n<div dir=\"ltr\">\n<div>Assume you are a \"lucky\" guy that your Java application interfaces with a C\/C++ application (e.g. a kind of server) which sends you some kind of TCP\/UDP network messages you need to parse. An example such C\/C++ structure is shown below:<\/div>\n<p><!--more--><\/p>\n<div><code>enum Gender { MALE, FEMALE };<\/code><br \/>\n<code>struct msg {<\/code><br \/>\n<code>\u00a0 #ifdef INTEL_STYLE<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 UCHAR spare4:4;<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 UCHAR octal:3;<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 UCHAR bool:1;<\/code><br \/>\n<code>\u00a0 #else<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 UCHAR bool:1;<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 UCHAR octal:3;<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 UCHAR spare4:4;<\/code><br \/>\n<code>\u00a0 #endif<\/code><br \/>\n<code>\u00a0 UINT uint; \u00a0<\/code><br \/>\n<code>\u00a0 char str[5];<br \/>\nfloat flt;<\/code><br \/>\n<code>\u00a0 enum Gender gender;<br \/>\n}<\/code><\/div>\n<div>If your C\/C++ application runs on an INTEL (x86) based machine architecture, then you receive the bits as little\u00a0<a href=\"http:\/\/en.wikipedia.org\/wiki\/Endianness\" target=\"_blank\" rel=\"noopener\">endian<\/a>\u00a0(see INTEL_STYLE above), otherwise as big\u00a0<a href=\"http:\/\/en.wikipedia.org\/wiki\/Endianness\" target=\"_blank\" rel=\"noopener\">endian<\/a>\u00a0(e.g. SPARC machines). Note that the JVM is big\u00a0<a href=\"http:\/\/en.wikipedia.org\/wiki\/Endianness\" target=\"_blank\" rel=\"noopener\">endian<\/a>, too. In the following we assume a\u00a0<a href=\"http:\/\/en.wikipedia.org\/wiki\/Endianness\" target=\"_blank\" rel=\"noopener\">big-endian<\/a>\u00a0architecture.<br \/>\nIn this blog entry we are going to see how you can parse such a message in your receiving Java application.<\/div>\n<h3>What will you need?<\/h3>\n<div>\n<ul>\n<li>the\u00a0<a href=\"http:\/\/javolutinon.org\/\" target=\"_blank\" rel=\"noopener\">javolution<\/a>\u00a0library to parse the C\/C++ struct in Java<\/li>\n<li>a calculator that handles binaries, hexadecimals and decimals (Windows, Linux and MacOSX already provide such calculators. However, they don't handle decimal point numbers, so this\u00a0<a href=\"http:\/\/www.binaryconvert.com\/\" target=\"_blank\" rel=\"noopener\">online converter<\/a>\u00a0will prove useful, too).<\/li>\n<\/ul>\n<div>The following table shows how the C\/C++ data types correspond to Javolution\u00a0<a href=\"http:\/\/javolution.org\/target\/site\/apidocs\/javolution\/io\/Struct.html\" target=\"_blank\" rel=\"noopener\">Struct<\/a>.<\/div>\n<div><\/div>\n<div>\n<table cellspacing=\"0\" cellpadding=\"4\">\n<colgroup>\n<col width=\"203\" \/>\n<col width=\"224\" \/><\/colgroup>\n<thead>\n<tr valign=\"TOP\">\n<th width=\"203\">C<\/th>\n<th width=\"224\">Java (Javolution Struct)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">UCHAR<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">Unsigned8<\/div>\n<\/td>\n<\/tr>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">UWORD<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">Unsigned16<\/div>\n<\/td>\n<\/tr>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">UINT<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">Unsigned32<\/div>\n<\/td>\n<\/tr>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">byte<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">Signed8<\/div>\n<\/td>\n<\/tr>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">short<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">Signed16<\/div>\n<\/td>\n<\/tr>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">int<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">Signed32<\/div>\n<\/td>\n<\/tr>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">long<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">Signed64<\/div>\n<\/td>\n<\/tr>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">long long<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">Signed64<\/div>\n<\/td>\n<\/tr>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">float<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">Float32<\/div>\n<\/td>\n<\/tr>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">double<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">Float64<\/div>\n<\/td>\n<\/tr>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">pointer<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">Reference32<\/div>\n<\/td>\n<\/tr>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">char[]<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">UTF8String<\/div>\n<\/td>\n<\/tr>\n<tr valign=\"TOP\">\n<td width=\"203\">\n<div align=\"CENTER\">enum<\/div>\n<\/td>\n<td width=\"224\">\n<div align=\"CENTER\">Enum32<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<div><\/div>\n<div>\n<h3>Let's get started.<\/h3>\n<p>The following Java class represents the above C\/C++ struct in Java:<\/p>\n<\/div>\n<div>\n<div><code>import java.nio.ByteBuffer;<\/code><\/div>\n<p><code><br \/>\npublic class Message extends javolution.io.Struct { \u00a0 \u00a0\u00a0<\/code><\/p>\n<\/div>\n<div><code>\u00a0 \u00a0private final Unsigned8 bool = new Unsigned8(1); \u00a0 \u00a0\u00a0<\/code><\/div>\n<div><code>\u00a0 \u00a0private final Unsigned8 octal = new Unsigned8(3); \u00a0 \u00a0\u00a0<\/code><\/div>\n<div><code>\u00a0 \u00a0private final Unsigned8 spare2 = new Unsigned8(4);<br \/>\nprivate final Unsigned32 uint = new Unsigned32();<\/code><br \/>\n<code>\u00a0 \u00a0private final UTF8String str = new UTF8String(5);\u00a0<\/code><br \/>\n<code>\u00a0 \u00a0private final Float32 flt = new Float32();\u00a0\u00a0<\/code><br \/>\n<code>\u00a0 \u00a0private final Enum32\u00a0gender = new Enum32(Gender.values()); \u00a0<\/code><br \/>\n<code><br \/>\n<\/code><\/div>\n<div><code>\u00a0 \u00a0public Message (byte[] b) { \u00a0 \u00a0 \u00a0 \u00a0\u00a0<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0this.setByteBuffer(ByteBuffer.wrap(b), 0); \u00a0 \u00a0\u00a0<\/code><\/div>\n<div><code>\u00a0 \u00a0}<br \/>\n<\/code><\/div>\n<div><code>\u00a0 \u00a0public boolean getBool() { \u00a0 \u00a0 \u00a0<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0return bool.get() != 0; \u00a0 \u00a0\u00a0<\/code><\/div>\n<div><code>\u00a0 \u00a0}<br \/>\n<\/code><\/div>\n<div><code>\u00a0 \u00a0public int getOctal() { \u00a0 \u00a0 \u00a0 \u00a0\u00a0<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0return octal.get(); \u00a0 \u00a0\u00a0<\/code><\/div>\n<div>\n<p><code>\u00a0 \u00a0}\u00a0<\/code><\/p>\n<p><code>\u00a0 \u00a0public long getUInt() { \u00a0 \u00a0 \u00a0 \u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 \u00a0return uint.get();<\/code><br \/>\n<code>\u00a0 \u00a0}<\/code><\/p>\n<p>public String getStr() {<br \/>\n<code>\u00a0 \u00a0 \u00a0 \u00a0return str.get(); \u00a0 \u00a0\u00a0 \u00a0 \u00a0<\/code><br \/>\n<code>\u00a0 \u00a0}\u00a0\u00a0<\/code><\/p>\n<div>\n<div><code><br \/>\n<\/code><code>\u00a0 \u00a0public float getFlt() { \u00a0 \u00a0 \u00a0 \u00a0\u00a0<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0return flt.get(); \u00a0 \u00a0\u00a0<\/code><\/div>\n<p><code>\u00a0 \u00a0}\u00a0<\/code><br \/>\n<code><br \/>\n<\/code><br \/>\npublic Gender getGender() {<br \/>\nreturn gender.get();<br \/>\n}<br \/>\n}<\/p>\n<p>enum Gender { MALE, FEMALE };<\/p>\n<\/div>\n<div>\n<div>Our\u00a0Message\u00a0class corresponds to the C\u00a0msg\u00a0struct. It extends\u00a0javolution.io.Struct\u00a0which is an implementation of the\u00a0java.nio.ByteBuffer. This\u00a0<a href=\"http:\/\/worldmodscode.wordpress.com\/2012\/12\/14\/the-java-bytebuffer-a-crash-course\/\" target=\"_blank\" rel=\"noopener\">crash course<\/a>\u00a0about Java ByteBuffer provides useful background information.<\/div>\n<div>The C\/C++ struct starts with a\u00a0UCHAR\u00a0which corresponds to\u00a0Unsigned8, i.e. one byte. The numbers after the colons (:) denote how many bits inside the byte represent each field of the\u00a0UCHAR. Thus,\u00a0octal:3\u00a0means that 3 bits represent the octal field. This in Javolution is represented by\u00a0Unsigned8(3).<\/div>\n<div>UINT\u00a0is represented by\u00a0Unsigned32\u00a0in javolution, which is 4 bytes long.<br \/>\nThe string\u00a0char[5]\u00a0is represented by\u00a0UTF8String(5).\u00a0float\u00a0by\u00a0Float32.<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<p><!--more--><\/p>\n<p>Finally, the\u00a0enum\u00a0is represented by\u00a0Enum32.<\/p>\n<div><\/div>\n<div>Let's create a unit test to test the above:<\/div>\n<div><\/div>\n<div><\/div>\n<p><code>import org.junit.After;<br \/>\nimport org.junit.Before;<br \/>\nimport org.junit.Test;<\/code><\/p>\n<p>import static org.junit.Assert.assertEquals;<br \/>\nimport static org.junit.Assert.assertTrue;<\/p>\n<p>public class MessageTest {<\/p>\n<p>private Message msg;<\/p>\n<p>@Before<br \/>\npublic void setUp() {<br \/>\nbyte[] bb = new byte[] {<br \/>\n(byte) 0x90, \u00a0\/\/ 1001 0000<br \/>\n<code>\u00a0 \u00a0 \u00a0 \u00a0 (byte) 0x00, (byte) 0x00, (byte) 0x00, \/\/ alignment with previous!<br \/>\n(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, \/\/ uint<br \/>\n(byte) 0x48, (byte) 0x41, (byte) 0x4C, (byte) 0x4C, (byte) 0x4F, \/\/ str<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 \u00a0 (byte) 0x00, (byte) 0x00, (byte) 0x00, \/\/ alignment with previous!<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 \u00a0 (byte) 0x3F, (byte) 0xC0, (byte) 0x00, (byte) 0x00, \/\/ flt<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 \u00a0 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, \/\/ gender<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 };<br \/>\nmsg = new Message(bb);<br \/>\n}<\/code><\/p>\n<p>@After<br \/>\npublic void tearDown() {<br \/>\n}<\/p>\n<p>@Test<br \/>\npublic void testMessage() {<br \/>\nassertTrue(msg.getBool()); \u00a0 \u00a0 \u00a0 \u00a0 \/\/ 1 = true<br \/>\nassertEquals(1, msg.getOctal()); \u00a0 \/\/ 001<br \/>\n<code>\u00a0 \u00a0 \u00a0 \u00a0assertEquals(2, msg.getUInt()); \u00a0 \u00a0<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 \u00a0assertEquals(\"HALLO\", msg.getStr());<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 \u00a0assertEquals(1.5, msg.getFlt(), 0.0); \u00a0\u00a0<\/code><br \/>\n<code>\u00a0 \u00a0 \u00a0 \u00a0assertEquals(Gender.FEMALE, msg.getGender());<br \/>\n}<br \/>\n}<br \/>\n<\/code><br \/>\n<code><br \/>\n<\/code><\/p>\n<div>The first byte\u00a00x90\u00a0corresponds to the binary value\u00a01001 0000. The first bit (1) represents\u00a0bool:1, the next three (001) the\u00a0octal:3, and the last four (0000)\u00a0spare:4.<\/div>\n<div>Be careful of the alignment! 1 byte + 3 bytes (of alignment) and the next field (uint) starts at the 5th byte and not at the 2nd as you might have expected.<\/div>\n<div>\n<p>The next 4 bytes correspond to the\u00a0uint.\u00a0The next 5\u00a0<a href=\"http:\/\/www.asciitable.com\/\" target=\"_blank\" rel=\"noopener\">ASCII<\/a>\u00a0characters correspond to the string \"HALLO\". Again, another alignment, and then the float field. The last 4 bytes represent the\u00a0Gender\u00a0enum which contains the value 1, i.e.\u00a0Gender.FEMALE.<\/p>\n<h3>Packed<\/h3>\n<\/div>\n<div>\n<p>However, your data might be packed, i.e. no alignment\/padding is happening. To do this, you override theisPacked()\u00a0method of\u00a0javolution.io.Struct:<\/p>\n<p>public class Message extends javolution.io.Struct {<br \/>\n...<br \/>\n@Override<br \/>\npublic boolean isPacked() {<br \/>\nreturn true;<br \/>\n}<br \/>\n...<br \/>\n}<\/p>\n<p>Now your test case data should contain no padding in order to pass:<\/p>\n<div><code><code>import org.junit.After;<br \/>\nimport org.junit.Before;<br \/>\nimport org.junit.Test;<\/code><\/code>import static org.junit.Assert.assertEquals;<br \/>\nimport static org.junit.Assert.assertTrue;<code><code><\/code><\/code>public class MessageTest {<code><code><\/code><\/code>private Message msg;<\/p>\n<p><code><code><\/code><\/code>@Before<br \/>\npublic void setUp() {<br \/>\nbyte[] bb = new byte[] {<br \/>\n(byte) 0x90, \u00a0\/\/ 1001 0000<\/p>\n<\/div>\n<div><code>\u00a0 \u00a0\/\/ \u00a0 \u00a0(byte) 0x00, (byte) 0x00, (byte) 0x00, \/\/ alignment with previous!<br \/>\n(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, \/\/ uint<br \/>\n(byte) 0x48, (byte) 0x41, (byte) 0x4C, (byte) 0x4C, (byte) 0x4F, \/\/ str<\/code><\/div>\n<div><code>\u00a0 \u00a0\/\/ \u00a0 \u00a0(byte) 0x00, (byte) 0x00, (byte) 0x00, \/\/ alignment with previous!<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 (byte) 0x3F, (byte) 0xC0, (byte) 0x00, (byte) 0x00, \/\/ flt<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 \u00a0 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, \/\/ gender<\/code><\/div>\n<div><code><code>\u00a0 \u00a0 \u00a0};<br \/>\nmsg = new Message(bb);<br \/>\n}<\/code><\/code>@After<br \/>\npublic void tearDown() {<br \/>\n}<code><code><\/code><\/code>@Test<br \/>\npublic void testMessage() {<br \/>\nassertTrue(msg.getBool()); \u00a0 \u00a0 \u00a0 \u00a0 \/\/ 1 = true<br \/>\nassertEquals(1, msg.getOctal()); \u00a0 \/\/ 001<\/div>\n<div><code>\u00a0 \u00a0 \u00a0 assertEquals(2, msg.getUInt()); \u00a0 \u00a0<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 assertEquals(\"HALLO\", msg.getStr());<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 assertEquals(1.5, msg.getFlt(), 0.0); \u00a0\u00a0<\/code><\/div>\n<div><code>\u00a0 \u00a0 \u00a0 assertEquals(Gender.FEMALE, msg.getGender());<br \/>\n}<br \/>\n}<\/code><\/div>\n<h3>Conclusion<\/h3>\n<\/div>\n<div>This concludes what you need to know to parse a C\/C++ struct in Java. However, keep in mind the following gotchas of Javolution:<\/div>\n<div><\/div>\n<ul>\n<li>All\u00a0Structs should be declared final.<\/li>\n<li>Javolution\u00a0<i><b>doesn't<\/b><\/i>\u00a0support nested structs; you need to\u00a0<i>flaten<\/i>\u00a0your C\/C++ structs in Java. E.g.<\/li>\n<\/ul>\n<div><code>struct Identification {\u00a0<\/code><br \/>\n<code>\u00a0 \u00a0 byte b;\u00a0<\/code><br \/>\n<code>\u00a0 \u00a0 long l;\u00a0<\/code><br \/>\n<code>}\u00a0<\/code><br \/>\n<code>struct msg {\u00a0<\/code><br \/>\n<code>\u00a0 \u00a0 struct Identification id;\u00a0<\/code><br \/>\n<code>\u00a0 \u00a0 int i;\u00a0<\/code><br \/>\n<code>}<\/code><br \/>\n<code><br \/>\n<\/code><\/div>\n<p>should be represented by:\u00a0<code><\/code><\/p>\n<p>&nbsp;<\/p>\n<div><code>public class Message extends javolution.io.Struct { \u00a0 \u00a0\u00a0<\/code><\/div>\n<p><code><\/code><\/p>\n<div><code>\u00a0 \u00a0private final Signed8 b = new Signed8(); \u00a0 \u00a0\u00a0<\/code><\/div>\n<p><code><\/code><\/p>\n<div><code>\u00a0 \u00a0private final Signed64 l = new Signed64(); \u00a0 \u00a0\u00a0<\/code><\/div>\n<p><code><\/code><\/p>\n<div><code>\u00a0 \u00a0private final Signed32 i = new Signed32();\u00a0<\/code><br \/>\n<code>...<\/code><br \/>\n<code>}<\/code><\/div>\n<p><code><\/code><\/p>\n<ul>\n<li>Javolution\u00a0array\u00a0can only accept members of\u00a0<a href=\"http:\/\/struct.member\/\">Struct.Member<\/a>. The following will not work:<\/li>\n<\/ul>\n<p><code>\u00a0 private final Reference32[] refs = array(new Reference32[2]);<\/code><\/p>\n<div>and you need to replace it by:<\/div>\n<div><\/div>\n<p><code>\u00a0 private final Signed32[] refs = array(new Signed32[2]);<\/code><\/p>\n<div>The following won't work neither:<\/div>\n<div><\/div>\n<div>private final AStruct[] aStruct = array(new AStruct[2]);<\/div>\n<div><\/div>\n<p>Happy parsing!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u9019\u662f\u4e00\u7bc7\u5099\u4efd\u7684\u6587\u7ae0\uff0c\u9810\u9632\u4e4b\u5f8c\u6d88\u5931\uff0c\u4f86\u6e90\u662f http:\/\/jnkjava.blogspot.tw\/2013\/06 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-2365","post","type-post","status-publish","format-standard","hentry","category-5"],"_links":{"self":[{"href":"https:\/\/learn-house.idv.tw\/index.php?rest_route=\/wp\/v2\/posts\/2365"}],"collection":[{"href":"https:\/\/learn-house.idv.tw\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/learn-house.idv.tw\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/learn-house.idv.tw\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/learn-house.idv.tw\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2365"}],"version-history":[{"count":0,"href":"https:\/\/learn-house.idv.tw\/index.php?rest_route=\/wp\/v2\/posts\/2365\/revisions"}],"wp:attachment":[{"href":"https:\/\/learn-house.idv.tw\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2365"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/learn-house.idv.tw\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2365"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/learn-house.idv.tw\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2365"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}